Global Search and Quick Filter in AG-Grid with React
Efficiently navigating large datasets is a cornerstone of a great user experience in any data-driven application. AG-Grid provides a robust and easy-to-implement feature for just this purpose: the Quick Filter. This powerful client-side search mechanism allows users to rapidly find relevant information by typing into a single input field, which then filters the entire grid's content.
In this installment of our AG-Grid React series, we'll dive deep into setting up and customizing the Quick Filter, empowering your users with instant, global search capabilities right within their React applications.
Understanding AG-Grid's Quick Filter
The Quick Filter in AG-Grid is designed for speed and simplicity. When active, it scans all visible columns (by default) for the text entered by the user. If any cell in a row contains the search string, that row is displayed; otherwise, it's hidden. This provides a highly intuitive "Google-like" search experience directly within your data grid.
Key characteristics:
- Client-Side: All filtering occurs directly in the user's browser, making it very fast for datasets that are already loaded.
- Global Scope: By default, it searches across all text-based column definitions.
- Case-Insensitive: Matches are typically case-insensitive, providing a more forgiving search.
- Simple API: Easily controlled via a single AG-Grid API method.
Setting Up Your React Component
Before we integrate the Quick Filter, let's assume you have a basic AG-Grid setup in your React component. If you're new to AG-Grid with React, please refer to previous articles in this series.
Here's a standard starting point for our React component:
import React, { useState, useRef, useEffect, 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] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price' },
{ field: 'year' },
]);
useEffect(() => {
// Simulate fetching data
const fetchData = async () => {
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2022 },
{ make: 'BMW', model: 'M5', price: 60000, year: 2020 },
{ make: 'Mercedes', model: 'C-Class', price: 45000, year: 2021 },
{ make: 'Audi', model: 'A4', price: 42000, year: 2022 },
{ make: 'Volkswagen', model: 'Golf', price: 28000, year: 2020 },
];
setRowData(data);
};
fetchData();
}, []);
const onGridReady = useCallback((params) => {
// We'll use params.api later to access the grid API
}, []);
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
onGridReady={onGridReady}
// quickFilterText={quickFilterValue} <-- We'll add this soon
/>
</div>
);
};
export default MyGridComponent;
Implementing the Global Search Input
The core of the Quick Filter involves two main parts: an input field where the user types their search query, and a way to tell AG-Grid what that query is.
The Search Input Field
We'll add a simple text input above our grid. We'll use React's `useState` hook to manage its value.
import React, { useState, useRef, useEffect, 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] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price' },
{ field: 'year' },
]);
const [quickFilterText, setQuickFilterText] = useState(''); // State for our search input
useEffect(() => {
// ... (fetchData remains the same)
const fetchData = async () => {
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2022 },
{ make: 'BMW', model: 'M5', price: 60000, year: 2020 },
{ make: 'Mercedes', model: 'C-Class', price: 45000, year: 2021 },
{ make: 'Audi', model: 'A4', price: 42000, year: 2022 },
{ make: 'Volkswagen', model: 'Golf', price: 28000, year: 2020 },
];
setRowData(data);
};
fetchData();
}, []);
const onGridReady = useCallback((params) => {
// We'll use params.api later to access the grid API
}, []);
const onFilterTextBoxChanged = useCallback((event) => {
setQuickFilterText(event.target.value); // Update our state
}, []);
return (
<div>
<div style={{ marginBottom: 10 }}>
Search: <input type="text" onChange={onFilterTextBoxChanged} value={quickFilterText} placeholder="Type to filter..." />
</div>
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
onGridReady={onGridReady}
// quickFilterText={quickFilterValue} <-- We'll add this soon
/>
</div>
</div>
);
};
export default MyGridComponent;
Connecting to AG-Grid's API
AG-Grid exposes its `gridApi` object when the grid is ready. We can use the `onGridReady` callback to store this API and then call `gridApi.setQuickFilter(value)` whenever our input's value changes.
import React, { useState, useRef, useEffect, 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] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price' },
{ field: 'year' },
]);
const [quickFilterText, setQuickFilterText] = useState(''); // State for our search input
useEffect(() => {
// Simulate fetching data
const fetchData = async () => {
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2022 },
{ make: 'BMW', model: 'M5', price: 60000, year: 2020 },
{ make: 'Mercedes', model: 'C-Class', price: 45000, year: 2021 },
{ make: 'Audi', model: 'A4', price: 42000, year: 2022 },
{ make: 'Volkswagen', model: 'Golf', price: 28000, year: 2020 },
];
setRowData(data);
};
fetchData();
}, []);
const onGridReady = useCallback((params) => {
// Store the gridApi in the ref once it's ready
gridRef.current.api = params.api;
}, []);
const onFilterTextBoxChanged = useCallback((event) => {
const value = event.target.value;
setQuickFilterText(value);
if (gridRef.current.api) {
gridRef.current.api.setQuickFilter(value); // Apply the filter
}
}, []);
return (
<div>
<div style={{ marginBottom: 10 }}>
Search: <input type="text" onChange={onFilterTextBoxChanged} value={quickFilterText} placeholder="Type to filter..." />
</div>
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
onGridReady={onGridReady}
// quickFilterText prop is an alternative, but using API directly is often preferred for dynamic control
/>
</div>
</div>
);
};
export default MyGridComponent;
With this setup, as you type into the search box, the grid will instantly filter its rows based on your input!
Enhancing the User Experience: Debouncing the Input
While instant filtering is great, constantly calling `setQuickFilter` on every keystroke for very large datasets or complex grids can sometimes impact performance. A common optimization is to "debounce" the input. Debouncing delays the execution of a function until a certain amount of time has passed since the last invocation. This means the filter will only apply after the user has paused typing for a short period (e.g., 300ms).
Let's create a simple custom hook for debouncing:
// hooks/useDebounce.js
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
Now, integrate `useDebounce` into our `MyGridComponent`:
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import useDebounce from './hooks/useDebounce'; // Import our custom hook
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] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price' },
{ field: 'year' },
]);
const [inputSearchText, setInputSearchText] = useState('');
const debouncedSearchText = useDebounce(inputSearchText, 300); // Debounce by 300ms
useEffect(() => {
// Simulate fetching data
const fetchData = async () => {
const data = [
{ make: 'Toyota', model: 'Celica', price: 35000, year: 2020 },
{ make: 'Ford', model: 'Mondeo', price: 32000, year: 2021 },
{ make: 'Porsche', model: 'Boxster', price: 72000, year: 2022 },
{ make: 'BMW', model: 'M5', price: 60000, year: 2020 },
{ make: 'Mercedes', model: 'C-Class', price: 45000, year: 2021 },
{ make: 'Audi', model: 'A4', price: 42000, year: 2022 },
{ make: 'Volkswagen', model: 'Golf', price: 28000, year: 2020 },
];
setRowData(data);
};
fetchData();
}, []);
const onGridReady = useCallback((params) => {
gridRef.current.api = params.api;
}, []);
// Effect to apply the debounced filter whenever debouncedSearchText changes
useEffect(() => {
if (gridRef.current.api) {
gridRef.current.api.setQuickFilter(debouncedSearchText);
}
}, [debouncedSearchText]); // Re-run effect only when debouncedSearchText changes
const onFilterTextBoxChanged = useCallback((event) => {
setInputSearchText(event.target.value); // Update instant input state
}, []);
return (
<div>
<div style={{ marginBottom: 10 }}>
Search: <input type="text" onChange={onFilterTextBoxChanged} value={inputSearchText} placeholder="Type to filter..." />
</div>
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
onGridReady={onGridReady}
/>
</div>
</div>
);
};
export default MyGridComponent;
Now, the filter will only update after a brief pause in typing, leading to a smoother experience, especially with more complex grids.
Customizing Quick Filter Behavior
Sometimes, you don't want the Quick Filter to search every single column. AG-Grid provides options to control which columns participate in the search and even how their values are interpreted.
Filtering Specific Columns Only
You can specify that certain columns should be ignored by the Quick Filter, or define custom text that should be used for searching for a particular column.
-
colDef.suppressQuickFilter = true:
Setting this on a column definition will completely exclude that column from the quick filter.
const [columnDefs] = useState([
{ field: 'make', suppressQuickFilter: true }, // 'make' column will be ignored by quick filter
{ field: 'model' },
{ field: 'price' },
{ field: 'year' },
]);
-
colDef.getQuickFilterText:
This function allows you to provide custom text that AG-Grid should use for the quick filter for a given cell. This is incredibly powerful if you have complex data, objects, or want to combine multiple fields into a single searchable string.
const [columnDefs] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price',
getQuickFilterText: (params) => {
// For price, let's only search for values over 50000
return params.value > 50000 ? String(params.value) : '';
}
},
{ field: 'year' },
]);
In this example, only prices above 50,000 will contribute to the quick filter search for the 'price' column.
You could also combine fields:
const [columnDefs] = useState([
{ field: 'make' },
{ field: 'model' },
{ field: 'price' },
{ field: 'year',
getQuickFilterText: (params) => {
// Combine year and make for search in this column's context
return `${params.data.make} ${params.value}`;
}
},
]);
Best Practices and Performance Tips
- Debounce for Large Grids: Always consider debouncing your quick filter input, especially for grids with hundreds or thousands of rows.
- Client-Side vs. Server-Side: The Quick Filter is a client-side feature. For extremely large datasets (tens of thousands or millions of rows) where loading all data upfront isn't feasible, you'll need to implement server-side filtering, often in conjunction with AG-Grid's Server-Side Row Model.
- User Feedback: Displaying the current filter text clearly to the user helps them understand why certain rows are visible or hidden.
- Clarity with `getQuickFilterText`: Use `getQuickFilterText` judiciously. While powerful, overly complex logic can make the filter behavior less intuitive for end-users.
Conclusion
The Quick Filter is a fantastic built-in feature of AG-Grid that drastically improves the usability of your data tables. By providing a global search mechanism with minimal setup, you empower your users to find the information they need swiftly. With the added benefit of debouncing and flexible column customization, you can fine-tune this feature for optimal performance and user experience in your React applications.
Continue experimenting with `getQuickFilterText` to unlock even more tailored search experiences, and keep building robust, user-friendly grids with AG-Grid!