Data grids are the backbone of many enterprise applications, providing users with powerful tools to view, analyze, and interact with large datasets. AG-Grid, especially when integrated with React, stands out for its extensive feature set and flexibility. In this fifteenth installment of our AG-Grid React series, we'll delve into two fundamental features that significantly enhance data discoverability and user experience: multi-column sorting and filtering.
While single-column sorting and filtering are standard, the ability to combine these operations across multiple columns unlocks a much deeper level of data exploration. Imagine sorting your sales data first by "Region" and then by "Order Date", or filtering employees by "Department" and then by "Status". This precision is what we'll master today.
Unlocking Data Potential with Multi-Column Sorting
Multi-column sorting allows users to sort data based on the values in more than one column. For instance, you might want to sort a list of products by their Category, and then within each category, by their Price. AG-Grid provides robust support for this, both through user interaction and programmatically.
Enabling Multi-Column Sorting
By default, AG-Grid allows single-column sorting. To enable multi-column sorting, you simply need to configure the multiSortKey property on your grid options. This property dictates the key that, when held down during a click on a column header, triggers a multi-column sort. The most common choice is 'ctrl' (for Ctrl+Click) or 'shift' (for Shift+Click).
Here's how you'd configure it in your React component:
import React, { useState, useRef, 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 MultiSortGrid = () => {
const gridRef = useRef();
const [rowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021, region: 'South' },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2019, region: 'North' },
{ make: 'BMW', model: 'M50', price: 60000, year: 2022, region: 'East' },
{ make: 'Mercedes', model: 'C63', price: 50000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Focus', price: 25000, year: 2021, region: 'West' },
]);
const [columnDefs] = useState([
{ field: 'region', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true },
{ field: 'make', sortable: true, filter: true },
{ field: 'model', sortable: true, filter: true },
{ field: 'price', sortable: true, filter: true },
]);
// This is the key setting for multi-column sorting
const defaultColDef = {
flex: 1,
minWidth: 100,
resizable: true,
sortable: true,
filter: true,
};
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
multiSortKey={'shift'} // <-- Enable multi-sort with Shift + Click
animateRows={true}
/>
</div>
);
};
export default MultiSortGrid;
With multiSortKey={'shift'}, your users can now:
- Click a column header to sort by that column (e.g., 'Region').
- Hold down the Shift key and click another column header (e.g., 'Year'). The grid will then sort by 'Region' first, and then by 'Year' within each region.
- Repeat with more columns as needed. The sort order is indicated by small numbers next to the sort arrows in the column headers.
Programmatic Multi-Column Sorting
Sometimes you need to apply a specific multi-column sort order programmatically, perhaps based on user preferences or a saved state. AG-Grid provides the api.applyColumnState() method for this, allowing you to define the sort model directly.
import React, { useState, useRef, 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 ProgrammaticMultiSortGrid = () => {
const gridRef = useRef();
const [rowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021, region: 'South' },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2019, region: 'North' },
{ make: 'BMW', model: 'M50', price: 60000, year: 2022, region: 'East' },
{ make: 'Mercedes', model: 'C63', price: 50000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Focus', price: 25000, year: 2021, region: 'West' },
]);
const [columnDefs] = useState([
{ field: 'region', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true },
{ field: 'make', sortable: true, filter: true },
{ field: 'model', sortable: true, filter: true },
{ field: 'price', sortable: true, filter: true },
]);
const defaultColDef = { flex: 1, minWidth: 100, resizable: true, sortable: true, filter: true };
const onGridReady = useCallback((params) => {
// You can apply sorting on grid ready or triggered by a button/event
// params.api.applyColumnState({
// state: [
// { colId: 'region', sort: 'asc', sortIndex: 0 },
// { colId: 'year', sort: 'desc', sortIndex: 1 },
// ],
// defaultState: { sort: null },
// });
}, []);
const applyMultiSort = useCallback(() => {
if (gridRef.current.api) {
gridRef.current.api.applyColumnState({
state: [
{ colId: 'region', sort: 'asc', sortIndex: 0 }, // First sort by region ascending
{ colId: 'year', sort: 'desc', sortIndex: 1 }, // Then by year descending
],
defaultState: { sort: null }, // Clear any existing sort on other columns
});
}
}, []);
const clearSorts = useCallback(() => {
if (gridRef.current.api) {
gridRef.current.api.applyColumnState({
defaultState: { sort: null },
});
}
}, []);
return (
<div style={{ height: 400, width: 600 }}>
<div style={{ marginBottom: 10 }}>
<button onClick={applyMultiSort} style={{ marginRight: 5 }}>Apply Region ASC, Year DESC Sort</button>
<button onClick={clearSorts}>Clear All Sorts</button>
</div>
<div className="ag-theme-alpine" style={{ height: '100%', width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
animateRows={true}
/>
</div>
</div>
);
};
export default ProgrammaticMultiSortGrid;
In the applyMultiSort function, we define an array of sort states. Each object in the array specifies the colId, the sort direction ('asc' or 'desc'), and crucially, the sortIndex. The sortIndex determines the precedence of the sort. A lower sortIndex means higher precedence. The defaultState: { sort: null } ensures that any previously sorted columns not specified in the state array have their sorts cleared.
To clear all sorts, you can simply call applyColumnState with a defaultState that clears the sort, as shown in the clearSorts example.
Mastering Multi-Column Filtering
When we talk about multi-column filtering, we mean applying filters independently to various columns simultaneously. For example, you might want to see all vehicles made by 'Ford' AND built in '2021'. AG-Grid's filter model inherently supports this, as each column maintains its own filter state.
Understanding Independent Column Filters
Unlike sorting, filtering is naturally a multi-column operation. When you apply a filter to one column (e.g., 'Make' is 'Ford'), it doesn't clear filters on other columns. The grid simply shows rows that satisfy all currently active filters.
Applying Filters via UI
Users can apply filters by clicking the filter icon in the column header (assuming filter: true is set on the colDef). AG-Grid offers various filter types (Text, Number, Date, Set, Multi) which can be configured. Each filter operates independently.
For example, a user could:
- Open the filter for the 'Region' column and select 'North'.
- Then, open the filter for the 'Year' column and select '2020'.
The grid would then display only the rows where the region is 'North' AND the year is '2020'.
Programmatic Multi-Column Filtering
Similar to sorting, you can programmatically apply complex filter models using the api.setFilterModel() method. This is incredibly useful for saving/loading user filter preferences or applying default filters.
import React, { useState, useRef, 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 ProgrammaticMultiFilterGrid = () => {
const gridRef = useRef();
const [rowData] = useState([
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021, region: 'South' },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2019, region: 'North' },
{ make: 'BMW', model: 'M50', price: 60000, year: 2022, region: 'East' },
{ make: 'Mercedes', model: 'C63', price: 50000, year: 2020, region: 'North' },
{ make: 'Ford', model: 'Focus', price: 25000, year: 2021, region: 'West' },
]);
const [columnDefs] = useState([
{ field: 'region', sortable: true, filter: true },
{ field: 'year', sortable: true, filter: true },
{ field: 'make', sortable: true, filter: true },
{ field: 'model', sortable: true, filter: true },
{ field: 'price', sortable: true, filter: true },
]);
const defaultColDef = { flex: 1, minWidth: 100, resizable: true, sortable: true, filter: true };
const applyMultiFilter = useCallback(() => {
if (gridRef.current.api) {
const filterModel = {
region: {
type: 'equals',
filter: 'North',
},
year: {
type: 'greaterThanOrEqual',
filter: 2020,
},
make: {
type: 'startsWith',
filter: 'F',
}
};
gridRef.current.api.setFilterModel(filterModel);
gridRef.current.api.onFilterChanged(); // Important: call this to apply the filter
}
}, []);
const clearFilters = useCallback(() => {
if (gridRef.current.api) {
gridRef.current.api.setFilterModel(null); // Or an empty object {}
gridRef.current.api.onFilterChanged();
}
}, []);
return (
<div style={{ height: 400, width: 600 }}>
<div style={{ marginBottom: 10 }}>
<button onClick={applyMultiFilter} style={{ marginRight: 5 }}>Apply Complex Filter</button>
<button onClick={clearFilters}>Clear All Filters</button>
</div>
<div className="ag-theme-alpine" style={{ height: '100%', width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
animateRows={true}
/>
</div>
</div>
);
};
export default ProgrammaticMultiFilterGrid;
The filterModel is an object where keys are colIds (field names in this case) and values are objects describing the filter for that column. The structure of the filter object depends on the filter type (e.g., text filter has type and filter properties). After setting the filter model, it's crucial to call api.onFilterChanged() to instruct the grid to re-apply the filters and update the view.
To clear all filters, you can pass null or an empty object {} to setFilterModel(), followed by onFilterChanged().
Combining Sorting and Filtering for Advanced Data Views
The true power of AG-Grid lies in the seamless interplay between its features. Multi-column sorting and filtering work perfectly together, allowing users to drill down into their data with precision. When you apply filters, the grid reduces the dataset to only matching rows. Any sorting, whether single or multi-column, then applies to this filtered subset of data.
This combined approach enables users to answer complex questions:
- "Show me all 'North' region vehicles from 2020 or later, sorted by 'Make' then by 'Price'."
- "What are the most expensive 'Ford' models, listed from highest to lowest price, within the 'South' region?"
AG-Grid handles the order of operations automatically: filters are applied first to narrow down the dataset, and then sorts are applied to the remaining visible rows.
Best Practices and Performance Considerations
While powerful, it's good to keep a few things in mind when implementing multi-column sorting and filtering:
- Performance with Large Datasets: For grids with tens of thousands or hundreds of thousands of rows, client-side sorting and filtering can become resource-intensive. For such scenarios, consider implementing Server-Side Row Models. This offloads sorting and filtering logic to your backend, significantly improving frontend performance.
- User Experience: Clearly communicate to users how to perform multi-column sorting (e.g., "Shift+Click column headers to add to sort"). The visual indicators in AG-Grid (sort numbers) are helpful, but initial guidance can enhance usability.
- Custom Sort/Filter Logic: For advanced sorting or filtering requirements beyond standard types (e.g., sorting based on a derived value, or a custom text filter), AG-Grid allows you to implement custom sort comparators and custom filters.
Conclusion
Multi-column sorting and filtering are indispensable features for any robust data grid. AG-Grid for React makes implementing these capabilities straightforward, offering both intuitive user interaction and powerful programmatic control. By leveraging these features, you can significantly enhance the analytical power and user experience of your data-driven applications, allowing users to gain deeper insights from their data with ease and precision.
```