Filtering
Text/number/date/boolean filters, custom filters
Filtering is on by default. Each column header shows a small funnel icon; clicking it opens a popover where the user picks an operator (like contains, greater than, between) and types a value. This chapter explains how to configure filter types and behaviour.
Jargon check — three words you'll see throughout this chapter:
- Filter — a rule that hides rows that don't match, leaving only the ones you care about. "Show only rows where Salary is greater than 50,000."
- Operator — the kind of comparison: contains, equals, greater than, between, and so on. Different data types offer different operators (you can't say a name is "greater than" another in a useful way).
- Popover — the little floating panel that appears anchored to the funnel icon when you click it. It holds the operator dropdown and the value input(s).
Grid-level filtering options
These go in the filtering group:
| Property | Type | Default | Meaning |
|---|---|---|---|
enabled |
boolean |
true |
Turn filtering on/off for the whole grid |
caseSensitive |
boolean |
false |
Match upper/lower case exactly |
debounceMs |
number |
300 |
Wait time (ms) before applying while typing |
iconPosition |
'left' | 'right' |
'right' |
Where the funnel icon sits |
showIconOnHover |
boolean |
false |
Only show the icon on hover |
filterIcon |
ReactNode |
— | Replace the default funnel icon |
activeFilterIcon |
ReactNode |
— | Icon shown when a filter is active |
popoverPlacement |
'bottom' | 'top' | 'auto' |
'auto' |
Where the popover opens |
const grid = createGrid<User>({
columns,
data,
filtering: {
enabled: true,
caseSensitive: false,
debounceMs: 300,
popoverPlacement: 'auto',
},
})
What is
debounceMs? "Debouncing" means waiting until the user stops doing something before reacting. While someone types "ada" into a text filter, you don't want to re-filter ona, thenad, thenada— that is three passes over the data for one search. WithdebounceMs: 300, the grid waits 300 milliseconds after the last keystroke before filtering. Lower it for snappier feedback on small datasets; raise it if filtering large data feels janky.
What is
caseSensitive? "Case" means upper- vs lower-case letters. Whenfalse(the default), searching "ADA" also matches "ada" and "Ada" — usually what users expect. Set it totrueonly when the distinction genuinely matters (e.g. case-sensitive codes or IDs).
What is
popoverPlacement? It controls where the filter popover opens relative to the funnel icon.'auto'(default) is smart: it normally opens below, but flips above when there isn't enough room at the bottom of the screen. Use'top'or'bottom'only if you want to force one side.
Turn filtering off entirely
filtering: { enabled: false }
Filter types — the filter group on a column
The most important filter setting is type, because it changes which operators the user sees and how values are compared.
| Property | Type | Default | Meaning |
|---|---|---|---|
enabled |
boolean |
inherits grid | Allow filtering this column |
type |
'text' | 'number' | 'date' | 'boolean' |
'text' |
Which filter UI/operators to use |
customFilter |
(value, row) => boolean |
— | Your own match logic |
columns: [
{ field: 'name', header: 'Name', filter: { type: 'text' } },
{ field: 'salary', header: 'Salary', filter: { type: 'number' } },
{ field: 'joined', header: 'Joined', filter: { type: 'date' } },
{ field: 'active', header: 'Active', filter: { type: 'boolean' } },
]
Operators available per type
- text: contains, notContains, equals, notEquals, startsWith, endsWith, isEmpty, isNotEmpty
- number: equals, notEquals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, between, isEmpty, isNotEmpty
- date: equals, notEquals, before, after, between, isEmpty, isNotEmpty
- boolean: equals, isEmpty, isNotEmpty
betweenshows two inputs (from / to). The grid validates that from ≤ to and shows an error otherwise.
Disable filtering for one column
{ field: 'avatar', header: '', filter: { enabled: false } }
Custom filters with customFilter
When the built-in operators aren't enough, write your own match function. A customFilter runs once per row. It receives the cell value and the whole row, and returns a boolean: true keeps the row, false hides it. Because it gets the whole row, you can base the decision on other fields too — something the built-in operators can't do.
Example — a "VIP" column that matches when salary is high and the user is active:
{
field: 'salary',
header: 'Salary',
filter: {
type: 'number',
customFilter: (value, row) =>
Number(value) > 150_000 && (row as User).active,
},
}
Customizing the filter icon
Use your own icons (any React node — an SVG, an emoji, a component):
filtering: {
filterIcon: <span>⏷</span>, // normal state
activeFilterIcon: <span>🔍</span>, // when a filter is applied
showIconOnHover: true, // keep headers clean until hover
}
Controlling filters from code (the API)
The active filters are described by a filter model — an object keyed by column field, where each value is { type, operator, value } (plus valueTo for between). Reading or setting this object is how you save, restore, or preset filters from code.
// Set the entire filter model at once
api.setFilterModel({
name: { type: 'text', operator: 'contains', value: 'ada' },
salary: { type: 'number', operator: 'greaterThan', value: 50000 },
})
// Set/clear a single column's filter
api.setColumnFilter('name', {
type: 'text',
operator: 'startsWith',
value: 'A',
})
api.clearColumnFilter('name')
// Read current filters
const filters = api.getFilterModel()
// Clear everything
api.clearFilters()
Reacting to filter changes
events: {
onFilterChange: (model) => {
console.log('Filters changed:', model)
},
}
When a filter matches nothing
If a filter returns zero rows, the grid shows a "No matching results" empty state. You can customize that message — see Chapter 09 · Appearance.
Presetting filters on load
To open the grid already filtered — from a saved view, a URL, or a "My active users" shortcut button — set the model in onReady (or any time after) with api.setFilterModel:
const grid = createGrid<User>({
columns,
data,
events: {
onReady: (api) => {
const params = new URLSearchParams(window.location.search)
const dept = params.get('department')
if (dept) {
api.setColumnFilter('department', {
type: 'text',
operator: 'equals',
value: dept,
})
}
},
},
})
Combine this with onFilterChange (writing the model back to the URL or localStorage) for the same "restore on reload" pattern used for sorting in Chapter 03.
Filtering a computed column
Just like sorting, filtering a value.getter column filters on whatever the getter returns, not the raw row — so you can filter on a value that isn't a real field:
{
field: 'fullName',
header: 'Name',
value: {
getter: (row) => `${row.firstName} ${row.lastName}`,
},
filter: { type: 'text' }, // filters against the computed full name
}
If the built-in contains/equals operators aren't precise enough for a computed value, reach for customFilter instead — it receives the getter's output as value and the full row as the second argument, so you can inspect the original fields too:
{
field: 'fullName',
header: 'Name',
value: { getter: (row) => `${row.firstName} ${row.lastName}` },
filter: {
customFilter: (value, row) =>
String(value).toLowerCase().startsWith((row as User).firstName.toLowerCase()),
},
}
Live example
A text filter, a number filter, and a date filter on three different columns — click a header's funnel icon to open its filter. All three combine with AND, so narrowing one column narrows the visible rows for the others too.
import { createGrid, Grid } from '@elitegrid/react'
import '@elitegrid/react/styles.css'
interface Book {
id: number
title: string
author: string
price: number
published: string
}
const books: Book[] = [
{ id: 1, title: 'The Pragmatic Programmer', author: 'Hunt & Thomas', price: 44.99, published: '1999-10-30' },
{ id: 2, title: 'Clean Code', author: 'Robert Martin', price: 39.99, published: '2008-08-01' },
{ id: 3, title: 'Refactoring', author: 'Martin Fowler', price: 54.99, published: '1999-07-08' },
{ id: 4, title: 'Design Patterns', author: 'Gang of Four', price: 59.99, published: '1994-10-21' },
{ id: 5, title: 'The Mythical Man-Month', author: 'Fred Brooks', price: 34.99, published: '1975-01-01' },
{ id: 6, title: 'Domain-Driven Design', author: 'Eric Evans', price: 49.99, published: '2003-08-30' },
{ id: 7, title: 'Effective Java', author: 'Joshua Bloch', price: 54.99, published: '2001-05-08' },
{ id: 8, title: "You Don't Know JS", author: 'Kyle Simpson', price: 29.99, published: '2014-12-27' },
{ id: 9, title: 'Working Effectively with Legacy Code', author: 'Michael Feathers', price: 47.99, published: '2004-09-22' },
{ id: 10, title: 'Continuous Delivery', author: 'Humble & Farley', price: 52.99, published: '2010-08-06' },
]
const grid = createGrid<Book>({
columns: [
{ field: 'title', header: 'Title', size: { flex: 2 }, filter: { type: 'text' } },
{ field: 'author', header: 'Author', size: { flex: 1.5 }, filter: { type: 'text' } },
{ field: 'price', header: 'Price', filter: { type: 'number' } },
{ field: 'published', header: 'Published', filter: { type: 'date' } },
],
data: books,
filtering: { enabled: true },
})
export default function App() {
return (
<div style={{ height: 440 }}>
<Grid grid={grid} />
</div>
)
}
Common filtering mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Typing feels laggy on a large dataset | debounceMs too low, or a heavy customFilter running on every keystroke |
Raise debounceMs, or move filtering to the server with a dataSource (see Chapter 14) |
| Filter box shows a value but nothing is filtered | You called setColumnFilter with a type that doesn't match the column's filter.type |
Make sure the model's type matches the column config exactly |
| "Between" filter never matches | value/valueTo swapped, or from > to |
The grid validates from ≤ to in its own UI — if you're setting the model from code, validate it yourself too |
| Filtering a formatted column doesn't match what's on screen | Filters run against the raw value, not the display.formatter output |
Filter against the underlying value/getter, or add a customFilter that mirrors the display logic |
Next: 05 · Pagination