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:

Defining the bool filter type with one emptyValue
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:

Defining the eq operator
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).

Specifying the FilterEditor component
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:

BoolFilterEditor
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.

Writing a `bool` filter type with a custom filter editor

The canDesign column is using a custom bool filter type with a custom filter editor.

View Mode
Fork
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>
    </>