AG-Grid-React-Series-#11-Sorting-in-AG-Grid-React
Welcome back to our AG-Grid React series! In this eleventh installment, we'll dive deep into one of the most fundamental and crucial features for any data table: sorting. Effective sorting allows users to quickly organize and make sense of large datasets, transforming raw information into actionable insights. AG-Grid provides a robust and flexible sorting mechanism, from basic single-column sorting to complex custom and server-side implementations.
By the end of this post, you'll understand how to:
- Enable basic sorting for your columns.
- Apply default sort orders programmatically.
- Control sorting using the AG-Grid API.
- Implement custom sorting logic with comparators.
- Configure multi-column sorting behavior.
- Understand the role of sorting in server-side data models.
1. Enabling Basic Sorting
The simplest way to enable sorting on a column is by setting the sortable: true property in its columnDef. Once enabled, users can click on the column header to cycle through ascending, descending, and no-sort states.
Here's how you enable sorting for a few columns:
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
const MyGridComponent = () => {
const gridRef = useRef();
const [rowData, setRowData] = useState([]);
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', sortable: true, filter: true },
{ field: 'model', sortable: true, filter: true },
{ field: 'price', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true }
]);
useEffect(() => {
// Fetch or generate your data here
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2019 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2021 },
{ make: 'BMW', model: 'M50', price: 60000, year: 2022 },
{ make: 'Audi', model: 'A3', price: 30000, year: 2020 },
];
setRowData(data);
}, []);
const defaultColDef = useMemo(() => ({
flex: 1,
minWidth: 100,
resizable: true,
}), []);
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
animateRows={true} // For smooth row animations during sort
></AgGridReact>
</div>
);
};
export default MyGridComponent;
With sortable: true, users can:
- Single-Column Sorting: Click a column header once for ascending, twice for descending, and a third time to remove the sort.
- Multi-Column Sorting: Hold down the
Ctrl(Windows/Linux) orCmd(macOS) key and click on multiple column headers. AG-Grid will apply sorting based on the order of clicks, creating a primary, secondary, etc., sort order.
2. Applying Default Sorting Programmatically
You might want your grid to load with a specific sort order already applied. This can be achieved by adding the sort property directly to your columnDef objects.
The sort property accepts values like 'asc' for ascending, 'desc' for descending, or null (or omit) for no initial sort. You can also define multi-column initial sorting by adding an sortIndex property.
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', sortable: true, filter: true },
// Sort 'model' column in ascending order initially
{ field: 'model', sortable: true, filter: true, sort: 'asc' },
// Sort 'price' column in descending order initially
{ field: 'price', sortable: true, filter: true, sort: 'desc' },
// Multi-column initial sort: 'year' ascending (primary), then 'make' descending (secondary)
{ field: 'year', sortable: true, filter: true, sort: 'asc', sortIndex: 0 },
{ field: 'make', sortable: true, filter: true, sort: 'desc', sortIndex: 1 }
]);
When using sortIndex, the column with the lower index is sorted first.
3. Controlling Sorting with the Grid API
AG-Grid provides a comprehensive API to interact with the grid programmatically, including managing sorting. This is useful for building custom UI controls or reacting to external events.
The key API methods for sorting are:
api.setSortModel(sortModel): Applies a new sort model to the grid.api.getSortModel(): Returns the current sort model applied to the grid.
A sortModel is an array of objects, where each object defines the sort state for a column:
[
{ colId: 'year', sort: 'asc' },
{ colId: 'make', sort: 'desc' }
]
Here's an example of how you might use these API methods:
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
const MyGridComponent = () => {
const gridRef = useRef();
const [rowData, setRowData] = useState([]);
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', sortable: true, filter: true },
{ field: 'model', sortable: true, filter: true },
{ field: 'price', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true }
]);
useEffect(() => {
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2019 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2021 },
{ make: 'BMW', model: 'M50', price: 60000, year: 2022 },
{ make: 'Audi', model: 'A3', price: 30000, year: 2020 },
];
setRowData(data);
}, []);
const defaultColDef = useMemo(() => ({
flex: 1,
minWidth: 100,
resizable: true,
}), []);
const onGridReady = useCallback((params) => {
// You can get the API here, though usually you'd access it via gridRef.current.api
// console.log('Grid is ready:', params.api);
}, []);
const applyDefaultSort = useCallback(() => {
const sortModel = [
{ colId: 'year', sort: 'asc' },
{ colId: 'price', sort: 'desc' }
];
gridRef.current.api.setSortModel(sortModel);
}, []);
const clearSort = useCallback(() => {
gridRef.current.api.setSortModel([]);
}, []);
const showCurrentSort = useCallback(() => {
const currentSortModel = gridRef.current.api.getSortModel();
alert('Current Sort Model: ' + JSON.stringify(currentSortModel));
}, []);
return (
<div>
<div style={{ marginBottom: '10px' }}>
<button onClick={applyDefaultSort} style={{ marginRight: '5px' }}>Apply Year ASC, Price DESC Sort</button>
<button onClick={clearSort} style={{ marginRight: '5px' }}>Clear Sort</button>
<button onClick={showCurrentSort}>Show Current Sort</button>
</div>
<div className="ag-theme-alpine" style={{ height: 400, width: 800 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
animateRows={true}
></AgGridReact>
</div>
</div>
);
};
export default MyGridComponent;
4. Implementing Custom Sorting Logic
By default, AG-Grid sorts strings alphabetically, numbers numerically, and dates chronologically. However, there are many scenarios where you need custom sorting logic:
- Sorting strings that contain numbers (e.g., 'item 10' should come after 'item 2').
- Sorting dates stored as strings in a non-standard format.
- Sorting complex objects based on a specific nested property or calculated value.
- Customizing the sort order of enumerated values.
You can provide a custom comparator function to the columnDef using the comparator property. This function works similarly to JavaScript's native Array.prototype.sort() comparator:
- It takes two values,
valueAandvalueB, from the cells being compared. - It should return:
-1ifvalueAshould come beforevalueB.1ifvalueAshould come aftervalueB.0ifvalueAandvalueBare considered equal.
Let's create a custom comparator to sort strings that represent version numbers (e.g., '1.10.0' should come after '1.2.0'):
const versionComparator = (versionA, versionB) => {
const partsA = versionA.split('.').map(Number);
const partsB = versionB.split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const numA = partsA[i] || 0;
const numB = partsB[i] || 0;
if (numA < numB) return -1;
if (numA > numB) return 1;
}
return 0;
};
const [columnDefs, setColumnDefs] = useState([
{ field: 'softwareVersion', sortable: true, comparator: versionComparator },
// ... other columns
]);
Or for dates stored as 'DD/MM/YYYY' strings:
const dateComparator = (date1, date2) => {
const parseDate = (dateString) => {
const parts = dateString.split('/');
return new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]));
};
const d1 = parseDate(date1);
const d2 = parseDate(date2);
if (d1 < d2) return -1;
if (d1 > d2) return 1;
return 0;
};
const [columnDefs, setColumnDefs] = useState([
{ field: 'releaseDate', sortable: true, comparator: dateComparator },
// ... other columns
]);
5. Multi-Column Sorting Configuration
AG-Grid provides options to customize how multi-column sorting behaves:
suppressMultiSort: true: Disables multi-column sorting for a specific column. This column will only allow single sorting.suppressCtrlSort: true: Disables theCtrl/Cmdkey requirement for multi-column sorting. Users can just click multiple headers to sort by multiple columns. This is often used in combination withunSortIcon: true.sortIcon: true(default): Shows the sort icon (arrow).unSortIcon: true: Displays a "no sort" icon (usually a faint arrow) to indicate that a column is sortable but currently not sorted.
Example configuration:
const [columnDefs, setColumnDefs] = useState([
{ field: 'make', sortable: true, filter: true, suppressMultiSort: true }, // Can only sort by 'make' alone
{ field: 'model', sortable: true, filter: true, unSortIcon: true }, // Shows 'no sort' icon when not sorted
{ field: 'price', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true }
]);
// ... in AgGridReact component
<AgGridReact
// ...
suppressCtrlSort={true} // Allows multi-sort without Ctrl/Cmd key globally
></AgGridReact>
6. Server-Side Sorting (Brief Overview)
For applications dealing with massive datasets (tens of thousands or millions of rows), it's impractical to load all data into the client-side grid for sorting. In such cases, sorting needs to be handled on the server. AG-Grid facilitates this through its Server-Side Row Model.
When using a server-side row model:
- The grid requests data from your server.
- When a user sorts a column, AG-Grid sends the current
sortModelto your server as part of the data request (e.g., in theparams.requestobject passed to yourgetRowscallback). - Your server-side logic applies the sorting criteria to the database query.
- The server returns the sorted, paginated (if applicable) data, which the grid then displays.
While the full implementation of server-side sorting is a complex topic that warrants its own deep dive (and will likely be covered in a future installment of this series), the key takeaway here is that AG-Grid provides the necessary hooks and API (specifically, passing the sortModel to your data source) to integrate seamlessly with your backend sorting logic.
Conclusion
Sorting is a powerful feature that greatly enhances the usability of any data grid. AG-Grid React offers a comprehensive set of tools to implement sorting, from simple click-and-sort functionality to sophisticated custom comparators and integration with server-side data models. By mastering these techniques, you can empower your users to efficiently navigate and analyze their data.
In our next post, we'll explore another essential feature: filtering, allowing users to narrow down their data based on specific criteria. Stay tuned!