Providing a Custom Filter Editor
Almost certainly, our current string
and number
filters are not enough for you. You will definitely need to write your custom filter editor.
Fortunately, doing this is straightforward - it involves using the useInfiniteColumnFilterEditor
hook.
The next snippet shows our implementation of the number
filter editor:
export function NumberFilterEditor<T>() {
const { ariaLabel, value, setValue, className, disabled } =
useInfiniteColumnFilterEditor<T>();
return (
<input
aria-label={ariaLabel}
type="number"
disabled={disabled}
value={value as any as number}
onChange={(event) => {
let value = isNaN(event.target.valueAsNumber)
? event.target.value
: event.target.valueAsNumber;
setValue(value as any as T);
}}
className={className}
/>
);
}
Note
This NumberFilterEditor
is configured in the components.FilterEditor
property for the number
filter type.
If you want to import the NumberFilterEditor
, you can do so with the following code:
import { components } from '@infinite-table/infinite-react';
const { NumberFilterEditor, StringFilterEditor } = components;
As an exercise, let's write a custom filter editor that shows a checkbox and uses that to filter the values.
First step is to define the bool
filter type:
filterTypes={{
bool: {
label: 'Boolean',
defaultOperator: 'eq',
// when the filter checkbox is indeterminate state, that's mapped to `null`
emptyValues: [null],
operators: [
// operators will come here
],
}
}}
Note in the code above, we have emptyValues: [null]
- so when the filter checkbox is in indeterminate state, it should show all the rows.
Now it's time to define the operators - more exactly, just one operator, eq
:
filterTypes={{
bool: {
defaultOperator: 'eq',
emptyValues: [null],
operators: [
{
name: 'eq',
label: 'Equals',
fn: ({ currentValue, filterValue }) => currentValue === filterValue,
},
],
},
}}
The last part of the bool
filter type will be to specify the FilterEditor
component - this can be either specified as part of the filter type or as part of the operator definition (each operator can override the components.FilterEditor
).
filterTypes={{
bool: {
defaultOperator: 'eq',
emptyValues: [null],
components: {
FilterEditor: BoolFilterEditor,
FilterOperatorSwitch: () => null,
},
operators: [
{
name: 'eq',
label: 'Equals',
fn: ({ currentValue, filterValue }) =>
currentValue === filterValue,
},
],
},
}}
Now it's time to write the actual BoolFilterEditor
that the bool
filter type is using:
import {
components,
useInfiniteColumnFilterEditor,
} from '@infinite-table/infinite-react';
const { CheckBox } = components;
function BoolFilterEditor() {
const { value, setValue, className } =
useInfiniteColumnFilterEditor<Developer>();
return (
<div className={className} style={{ textAlign: 'center' }}>
<CheckBox
checked={value}
onChange={(newValue) => {
if (value === true) {
// after the value was true, make it go to indeterminate state
newValue = null;
}
if (value === null) {
// from indeterminate, goto false
newValue = false;
}
setValue(newValue);
}}
/>
</div>
);
}
Note
In the snippet above, note how we're using the useInfiniteColumnFilterEditor
hook to get the current value
of the filter and also to retrieve the setValue
function that we need to call when we want to update filtering.
The canDesign
column is using a custom bool
filter type with a custom filter editor.
import * as React from 'react'; import { InfiniteTable, InfiniteTablePropColumns, DataSource, components, useInfiniteColumnFilterEditor, } from '@infinite-table/infinite-react'; const { CheckBox } = components; type Developer = { id: number; firstName: string; canDesign: boolean; stack: string; hobby: string; }; const dataSource: Developer[] = [ { id: 1, firstName: 'John', canDesign: true, stack: 'frontend', hobby: 'gaming', }, { id: 2, firstName: 'Jane', canDesign: false, stack: 'backend', hobby: 'reading', }, { id: 3, firstName: 'Jack', canDesign: true, stack: 'frontend', hobby: 'gaming', }, { id: 4, firstName: 'Jill', canDesign: false, stack: 'backend', hobby: 'reading', }, { id: 5, firstName: 'Seb', canDesign: false, stack: 'backend', hobby: 'reading', }, ]; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', type: 'number', defaultWidth: 100, }, canDesign: { field: 'canDesign', filterType: 'bool', renderValue: ({ value }) => (value ? 'Yes' : 'No'), }, firstName: { field: 'firstName', }, stack: { field: 'stack' }, }; const domProps = { style: { height: '100%', }, }; function BoolFilterEditor() { const { value, setValue, className } = useInfiniteColumnFilterEditor<Developer>(); return ( <div className={className} style={{ textAlign: 'center' }}> <CheckBox checked={value} onChange={(newValue) => { if (value === true) { // after the value was true, make it go to indeterminate state newValue = null; } if (value === null) { // from indeterminate, goto false newValue = false; } setValue(newValue); }} /> </div> ); } export default () => { return ( <> <React.StrictMode> <DataSource<Developer> data={dataSource} primaryKey="id" defaultFilterValue={[]} filterDelay={0} filterTypes={{ bool: { defaultOperator: 'eq', emptyValues: [null], components: { FilterEditor: BoolFilterEditor, FilterOperatorSwitch: () => null, }, operators: [ { name: 'eq', label: 'Equals', fn: ({ currentValue, filterValue }) => currentValue === filterValue, }, ], }, }} > <InfiniteTable<Developer> domProps={domProps} columnDefaultWidth={150} columnMinWidth={50} columns={columns} /> </DataSource> </React.StrictMode> </>