Skip to content

Commit eba5386

Browse files
authored
feat: new filter, sort, aggregation Fns, and recommended import patterns (TanStack#6415)
* feat: new filter, sort, aggregation Fns, and recommended import patterns * fix some examples
1 parent a6dcc22 commit eba5386

435 files changed

Lines changed: 7146 additions & 3842 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/framework/alpine/guide/column-filtering.md

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@ import {
2020
columnFilteringFeature,
2121
createFilteredRowModel,
2222
createTable,
23-
filterFns,
23+
filterFn_includesString,
24+
filterFn_inNumberRange,
2425
tableFeatures,
2526
} from '@tanstack/alpine-table'
2627

2728
const features = tableFeatures({
2829
columnFilteringFeature,
2930
filteredRowModel: createFilteredRowModel(), // if using client-side filtering
30-
filterFns,
31+
filterFns: {
32+
includesString: filterFn_includesString,
33+
inNumberRange: filterFn_inNumberRange,
34+
},
3135
})
3236

3337
const table = createTable({
@@ -39,6 +43,8 @@ const table = createTable({
3943
})
4044
```
4145

46+
> **Note:** The `filterFns` registry above lists only the built-in filter functions this table uses. Spreading the entire built-in `filterFns` registry (`filterFns: { ...filterFns }`) still works, but it puts every built-in filter function in your bundle. Register just the functions you use, or pass a function directly to the `filterFn` column option with no registration at all.
47+
4248
## Column Filtering (Alpine) Guide
4349

4450
Filtering comes in 2 flavors: Column Filtering and Global Filtering.
@@ -86,21 +92,25 @@ const table = createTable({
8692
8793
### Client-Side Filtering
8894

89-
If you are using the built-in client-side filtering features, add the `columnFilteringFeature` and the `filteredRowModel` factory to your features. Import `createFilteredRowModel` and `filterFns` from TanStack Table:
95+
If you are using the built-in client-side filtering features, add the `columnFilteringFeature` and the `filteredRowModel` factory to your features. Import `createFilteredRowModel` and the filter functions you need from TanStack Table:
9096

9197
```ts
9298
import {
9399
columnFilteringFeature,
94100
createFilteredRowModel,
95101
createTable,
96-
filterFns,
102+
filterFn_includesString,
103+
filterFn_inNumberRange,
97104
tableFeatures,
98105
} from '@tanstack/alpine-table'
99106

100107
const features = tableFeatures({
101108
columnFilteringFeature,
102109
filteredRowModel: createFilteredRowModel(),
103-
filterFns,
110+
filterFns: {
111+
includesString: filterFn_includesString,
112+
inNumberRange: filterFn_inNumberRange,
113+
},
104114
})
105115

106116
const table = createTable({
@@ -227,28 +237,34 @@ const table = createTable({
227237

228238
Each column can have its own unique filtering logic. Choose from any of the filter functions that are provided by TanStack Table, or create your own.
229239

230-
By default there are 12 built-in filter functions to choose from:
240+
By default there are 18 built-in filter functions to choose from:
231241

232242
- `includesString` - Case-insensitive string inclusion
233243
- `includesStringSensitive` - Case-sensitive string inclusion
244+
- `startsWith` - Case-insensitive string prefix match
245+
- `endsWith` - Case-insensitive string suffix match
234246
- `equalsString` - Case-insensitive string equality
247+
- `equalsStringSensitive` - Case-sensitive string equality
235248
- `equals` - Strict equality `===`
236249
- `weakEquals` - Weak equality `==`
250+
- `empty` - The row's value is nullish or whitespace-only (the filter value is an on/off flag)
251+
- `notEmpty` - The row's value is not nullish or whitespace-only (the filter value is an on/off flag)
237252
- `arrIncludes` - The row's array (or string) value includes at least one of the filter values
238253
- `arrIncludesAll` - The row's array value includes every filter value
239254
- `arrIncludesSome` - The row's array value includes at least one of the filter values
240255
- `arrHas` - The row's scalar value equals at least one of the filter values
241256
- `inNumberRange` - Inclusive `[min, max]` number range (endpoints normalized and swapped if reversed)
257+
- `inDateRange` - Inclusive `[min, max]` date range accepting `Date` objects, timestamps, or date strings (blank endpoints are open-ended)
242258
- `between` - Exclusive min/max range (blank endpoints are open-ended)
243259
- `betweenInclusive` - Inclusive min/max range (blank endpoints are open-ended)
244260

245-
You can also define your own custom filter functions, either inline as the `filterFn` column option, or by name in the filter function registry that you pass to `createFilteredRowModel`.
261+
You can also define your own custom filter functions, either inline as the `filterFn` column option, or by name in the `filterFns` registry slot on `tableFeatures`.
246262

247263
#### Custom Filter Functions
248264

249265
> **Note:** These filter functions only run during client-side filtering.
250266
251-
Whether you register a custom filter function in the registry passed to `createFilteredRowModel` or pass it directly as a `filterFn` column option, it should have the following signature:
267+
Whether you register a custom filter function in the `filterFns` slot on `tableFeatures` or pass it directly as a `filterFn` column option, it should have the following signature:
252268

253269
```ts
254270
const myCustomFilterFn: FilterFn<typeof features, MyData> = (
@@ -282,7 +298,7 @@ const columns = [
282298
{
283299
header: () => 'Birthday',
284300
accessorKey: 'birthday',
285-
filterFn: 'myCustomFilterFn', // reference a custom filter function registered with createFilteredRowModel
301+
filterFn: 'myCustomFilterFn', // reference a custom filter function registered in features
286302
},
287303
{
288304
header: () => 'Profile',
@@ -298,7 +314,8 @@ const features = tableFeatures({
298314
columnFilteringFeature,
299315
filteredRowModel: createFilteredRowModel(),
300316
filterFns: {
301-
...filterFns,
317+
includesString: filterFn_includesString,
318+
inNumberRange: filterFn_inNumberRange,
302319
myCustomFilterFn: (row, columnId, filterValue) => {
303320
return // true or false based on your custom logic
304321
},
@@ -321,34 +338,48 @@ const table = createTable({
321338

322339
You can attach a few other properties to filter functions to customize their behavior:
323340

324-
- `filterFn.resolveFilterValue` - This optional "hanging" method on any given `filterFn` allows the filter function to transform/sanitize/format the filter value before it is passed to the filter function.
341+
- `filterFn.resolveFilterValue` - This optional "hanging" method on any given `filterFn` allows the filter function to transform/sanitize/format the filter value before it is passed to the filter function. The table applies it once per filter (not once per row), so it is also the right place for expensive preparation work.
342+
343+
- `filterFn.resolveDataValue` - This optional "hanging" method normalizes each row's value before it is compared against the filter value. It is honored by every filter function built with the `constructFilterFn` helper, which includes all built-in filter functions.
325344

326345
- `filterFn.autoRemove` - This optional "hanging" method on any given `filterFn` is passed a filter value and expected to return `true` if the filter value should be removed from the filter state. eg. Some boolean-style filters may want to remove the filter value from the table state if the filter value is set to `false`.
327346

347+
The `constructFilterFn` helper builds a filter function from a value-level comparator plus those optional resolvers:
348+
328349
```ts
329-
const startsWithFilterFn = <
330-
TFeatures extends TableFeatures,
331-
TData extends RowData,
332-
>(
333-
row: Row<TFeatures, TData>,
334-
columnId: string,
335-
filterValue: string, // resolveFilterValue below transforms the raw value to a string
336-
) =>
337-
row
338-
.getValue<number | string>(columnId)
339-
.toString()
340-
.toLowerCase()
341-
.trim()
342-
.startsWith(filterValue) // toString, toLowerCase, and trim the filter value in `resolveFilterValue`
350+
const startsWithFilterFn = constructFilterFn({
351+
// compare the (resolved) row value against the (resolved) filter value
352+
filter: (dataValue, filterValue) =>
353+
Boolean(dataValue?.startsWith(filterValue)),
354+
// normalize the filter value once, before any rows are tested
355+
resolveFilterValue: (value) => String(value).toLowerCase().trim(),
356+
// normalize each row's value before it reaches the comparator
357+
resolveDataValue: (value) => String(value ?? '').toLowerCase(),
358+
// remove the filter value from filter state if it is falsy (empty string in this case)
359+
autoRemove: (value) => !value,
360+
})
361+
```
343362

344-
// remove the filter value from filter state if it is falsy (empty string in this case)
345-
startsWithFilterFn.autoRemove = (val: any) => !val
363+
Keeping the comparison in `filter` and the normalization in the resolvers pays off when you need a variant of an existing filter function: the definition is attached to the returned function, so you can spread any filter function built with `constructFilterFn` and override only what differs. For example, a version of `includesString` that also ignores diacritics (so a search for "eric" matches "Éric"):
346364

347-
// transform/sanitize/format the filter value before it is passed to the filter function
348-
startsWithFilterFn.resolveFilterValue = (val: any) =>
349-
val.toString().toLowerCase().trim()
365+
```ts
366+
const normalize = (value: unknown) =>
367+
String(value ?? '')
368+
.toLowerCase()
369+
.normalize('NFD')
370+
.replace(/\p{Diacritic}/gu, '')
371+
372+
const includesStringIgnoreDiacritics = constructFilterFn({
373+
...filterFn_includesString, // reuse the comparator and autoRemove behavior
374+
resolveFilterValue: normalize,
375+
resolveDataValue: normalize,
376+
})
350377
```
351378

379+
Register the variant by name in the `filterFns` registry or pass it directly to the `filterFn` column option, just like any other custom filter function.
380+
381+
> **Note:** The table applies `resolveFilterValue` once per filter before any rows are tested. If you ever call a filter function directly (outside of a table), resolve the filter value yourself: `myFilterFn(row, columnId, myFilterFn.resolveFilterValue?.(rawValue) ?? rawValue)`.
382+
352383
### Wiring up the filter UI
353384

354385
TanStack Table will not add filter inputs to your table. Add them yourself on real elements (Alpine does not initialize directives inside content set with `x-html`). Read the current value with `column.getFilterValue()` and write it with `column.setFilterValue()`. Use `column.getCanFilter()` to decide whether to render an input.
@@ -461,7 +492,10 @@ const features = tableFeatures({
461492
rowExpandingFeature,
462493
filteredRowModel: createFilteredRowModel(),
463494
expandedRowModel: createExpandedRowModel(),
464-
filterFns,
495+
filterFns: {
496+
includesString: filterFn_includesString,
497+
inNumberRange: filterFn_inNumberRange,
498+
},
465499
})
466500

467501
const table = createTable({
@@ -486,7 +520,10 @@ const features = tableFeatures({
486520
rowExpandingFeature,
487521
filteredRowModel: createFilteredRowModel(),
488522
expandedRowModel: createExpandedRowModel(),
489-
filterFns,
523+
filterFns: {
524+
includesString: filterFn_includesString,
525+
inNumberRange: filterFn_inNumberRange,
526+
},
490527
})
491528

492529
const table = createTable({

docs/framework/alpine/guide/fuzzy-filtering.md

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ import {
2020
createFilteredRowModel,
2121
createSortedRowModel,
2222
createTable,
23-
filterFns,
2423
globalFilteringFeature,
2524
rowSortingFeature,
26-
sortFns,
2725
tableFeatures,
2826
} from '@tanstack/alpine-table'
2927

@@ -33,8 +31,8 @@ const features = tableFeatures({
3331
rowSortingFeature,
3432
filteredRowModel: createFilteredRowModel(), // if using client-side filtering
3533
sortedRowModel: createSortedRowModel(), // if using client-side sorting
36-
filterFns,
37-
sortFns,
34+
filterFns: { fuzzy: fuzzyFilter },
35+
sortFns: { fuzzy: fuzzySort },
3836
})
3937

4038
const table = createTable({
@@ -46,6 +44,8 @@ const table = createTable({
4644
})
4745
```
4846

47+
> **Note:** The `filterFns` and `sortFns` registries above list only the custom `fuzzy` functions this guide uses. Spreading the entire built-in registries (`filterFns: { ...filterFns, fuzzy: fuzzyFilter }`) still works, but it puts every built-in function in your bundle. Register just the functions you use, or pass functions directly to the `filterFn` and `sortFn` column options with no registration.
48+
4949
## Fuzzy Filtering (Alpine) Guide
5050

5151
Fuzzy filtering is a technique that allows you to filter data based on approximate matches. This can be useful when you want to search for data that is similar to a given value, rather than an exact match.
@@ -124,8 +124,8 @@ const features = tableFeatures({
124124
rowSortingFeature,
125125
filteredRowModel: createFilteredRowModel(),
126126
sortedRowModel: createSortedRowModel(),
127-
filterFns: { ...filterFns, fuzzy: fuzzyFilter },
128-
sortFns,
127+
filterFns: { fuzzy: fuzzyFilter },
128+
sortFns: { fuzzy: fuzzySort },
129129
filterMeta: metaHelper<FuzzyFilterMeta>(),
130130
})
131131
```
@@ -140,11 +140,9 @@ import {
140140
createFilteredRowModel,
141141
createSortedRowModel,
142142
createTable,
143-
filterFns,
144143
globalFilteringFeature,
145144
metaHelper,
146145
rowSortingFeature,
147-
sortFns,
148146
tableFeatures,
149147
} from '@tanstack/alpine-table'
150148

@@ -154,8 +152,8 @@ const features = tableFeatures({
154152
rowSortingFeature,
155153
filteredRowModel: createFilteredRowModel(),
156154
sortedRowModel: createSortedRowModel(), // needed if you want sorting with fuzzy rank
157-
filterFns: { ...filterFns, fuzzy: fuzzyFilter },
158-
sortFns,
155+
filterFns: { fuzzy: fuzzyFilter },
156+
sortFns: { fuzzy: fuzzySort },
159157
filterMeta: metaHelper<FuzzyFilterMeta>(),
160158
})
161159

@@ -171,7 +169,7 @@ const table = createTable({
171169

172170
### Using Fuzzy Filtering with Column Filtering
173171

174-
To use fuzzy filtering with column filtering, pass your fuzzy filter function to `createFilteredRowModel` (merging it with the built-in `filterFns`). You can then specify the fuzzy filter by name in the `filterFn` option of the column definition:
172+
To use fuzzy filtering with column filtering, register your fuzzy filter function in the `filterFns` slot on `tableFeatures` (as shown above). You can then specify the fuzzy filter by name in the `filterFn` option of the column definition:
175173

176174
```ts
177175
const column = [
@@ -194,7 +192,7 @@ When using fuzzy filtering with column filtering, you might also want to sort th
194192

195193
```ts
196194
import { compareItems } from '@tanstack/match-sorter-utils'
197-
import { sortFns } from '@tanstack/alpine-table'
195+
import { sortFn_alphanumeric } from '@tanstack/alpine-table'
198196
import type { SortFn } from '@tanstack/alpine-table'
199197

200198
const fuzzySort: SortFn<FuzzyFeatures, Person> = (rowA, rowB, columnId) => {
@@ -209,7 +207,7 @@ const fuzzySort: SortFn<FuzzyFeatures, Person> = (rowA, rowB, columnId) => {
209207
}
210208

211209
// Provide an alphanumeric fallback for when the item ranks are equal
212-
return dir === 0 ? sortFns.alphanumeric(rowA, rowB, columnId) : dir
210+
return dir === 0 ? sortFn_alphanumeric(rowA, rowB, columnId) : dir
213211
}
214212
```
215213

@@ -228,4 +226,4 @@ You can then pass this sorting function directly to the `sortFn` option of the c
228226
}
229227
```
230228

231-
> **Note:** Unlike `filterFn: 'fuzzy'` above, `fuzzySort` is passed as a function rather than a string. A string reference like `sortFn: 'fuzzySort'` would only work if you also registered the function in the `sortFns` slot on `tableFeatures` (e.g. `sortFns: { ...sortFns, fuzzySort }`). Passing the function directly skips that step.
229+
> **Note:** Unlike `filterFn: 'fuzzy'` above, `fuzzySort` is passed as a function rather than a string. A string reference like `sortFn: 'fuzzySort'` would only work if you also registered the function in the `sortFns` slot on `tableFeatures` (e.g. `sortFns: { fuzzySort }`). Passing the function directly skips that step.

docs/framework/alpine/guide/global-filtering.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
columnFilteringFeature,
2121
createFilteredRowModel,
2222
createTable,
23-
filterFns,
23+
filterFn_includesString,
2424
globalFilteringFeature,
2525
tableFeatures,
2626
} from '@tanstack/alpine-table'
@@ -29,7 +29,7 @@ const features = tableFeatures({
2929
columnFilteringFeature,
3030
globalFilteringFeature,
3131
filteredRowModel: createFilteredRowModel(), // if using client-side filtering
32-
filterFns,
32+
filterFns: { includesString: filterFn_includesString },
3333
})
3434

3535
const table = createTable({
@@ -41,6 +41,8 @@ const table = createTable({
4141
})
4242
```
4343

44+
> **Note:** The `filterFns` registry above lists only the built-in filter function this table uses. Spreading the entire built-in `filterFns` registry (`filterFns: { ...filterFns }`) still works, but it puts every built-in filter function in your bundle. Register just the functions you use, or pass a function directly to the `globalFilterFn` option with no registration at all.
45+
4446
## Global Filtering (Alpine) Guide
4547

4648
Filtering comes in 2 flavors: Column Filtering and Global Filtering.
@@ -103,7 +105,7 @@ import {
103105
columnFilteringFeature,
104106
createFilteredRowModel,
105107
createTable,
106-
filterFns,
108+
filterFn_includesString,
107109
globalFilteringFeature,
108110
tableFeatures,
109111
} from '@tanstack/alpine-table'
@@ -112,7 +114,7 @@ const features = tableFeatures({
112114
columnFilteringFeature,
113115
globalFilteringFeature,
114116
filteredRowModel: createFilteredRowModel(),
115-
filterFns,
117+
filterFns: { includesString: filterFn_includesString },
116118
})
117119

118120
const table = createTable({
@@ -123,7 +125,7 @@ const table = createTable({
123125

124126
### Global Filter Function
125127

126-
The `globalFilterFn` option allows you to specify the filter function that will be used for global filtering. The filter function can be a string that references a built-in filter function, a string that references a custom filter function registered in the `filterFns` slot on `tableFeatures`, or a custom filter function passed directly.
128+
The `globalFilterFn` option allows you to specify the filter function that will be used for global filtering. The filter function can be a string that references a filter function (built-in or custom) registered in the `filterFns` slot on `tableFeatures`, or a filter function passed directly.
127129

128130
```ts
129131
const table = createTable({

0 commit comments

Comments
 (0)