Client-side Filtering

The most common way to use filtering in Infinite Table is by configuring filters for columns (this works both for client-side and server-side filtering).

If the DataSource data property is a function (and not an array or a Promise), then the filtering will happen server-side by default.

Note

To force client-side filtering, you can explicitly set the filterMode="local" property on the <DataSource /> component.

The possible values for this prop are:

  • filterMode="local" - filtering will happen client-side
  • filterMode="remote" - filtering will happen remotely and the filterValue will be passed as a property to the parameter object sent to the data function.

Showing the Column Filters

In order to show the column filter editors in the column headers, you need to specify either the uncontrolled defaultFilterValue property or the controlled filterValue version.

Client-side filtering in action

This example shows remote data with local filtering - it sets filterMode="local" on the <DataSource /> component.

In addition, the filterDelay property is set to 0 for instant feedback.

View Mode
Fork
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  defaultFilterTypes,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

defaultFilterTypes.string.operators.push({
  name: 'Not includes',
  label: 'Not Includes',
  fn: ({ currentValue, filterValue, emptyValues }) => {
    if (
      emptyValues.includes(currentValue) ||
      emptyValues.includes(filterValue)
    ) {
      return true;
    }
    return (
      typeof currentValue === 'string' &&
      typeof filterValue == 'string' &&
      !currentValue.toLowerCase().includes(filterValue.toLowerCase())
    );
  },
});

const data: DataSourceData<Developer> = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 70,
    defaultFilterable: false,
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
  currency: { field: 'currency' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
        >
          <InfiniteTable<Developer>
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>

Note

If you still want filtering to be enabled with the default functionality of using the filterValue (or uncontrolled defaultFilterValue), but want to hide the column filter editors, you can set the showColumnFilters property to false.

Using Filter Types

As already documented in the Understanding Filter Types section, you can specify the types of the filters the <DataSource /> will support, by using the filterTypes property.

The default filter types are string and number - read the next section to see how you can add new operators to those filter types.

A filter type is basically a collection of operators available for a type of data. Each operator needs a name and a function that will be used to filter the data, when that operator is applied.

Using_filter_types_for_filterValue
filterValue={[
  {
    field: 'firstName',
    filter: {
      type: 'string',
      operator: 'includes',
      value: 'John'
    }

  },
  {
    field: 'age',
    filter: {
      type: 'number',
      operator: 'gt',
      value: 30
    }
  }
]}

The above filter value specifies that there are 2 filters applied:

  • the firstName column applies a filter that will only match rows with firstName containining the string John
  • the age column has an additional filter, that will only match rows with age greater than 30

If filterMode is set to local, then the filtering will happen client-side, using the filtering functions specified by includes operator in the string filter type and the gt operator in the number filter type.

Here's a snippet of code from the string filter type showing the includes operator:

operators: [
  {
    name: 'includes',
    components: { Icon: /* a React Component */ },
    label: 'Includes',
    fn: ({ currentValue, filterValue }) => {
      return (
        typeof currentValue === 'string' &&
        typeof filterValue == 'string' &&
        currentValue.toLowerCase().includes(filterValue.toLowerCase())
      );
    },
  },
  //...
]

Let's now look at another example, of implementing a custom salary filter type.

For this, we override the filterTypes property of the <DataSource /> component:

const filterTypes = {
  salary: {
    defaultOperator: 'gt',
    emptyValues: ['', null, undefined],
    operators: [ /*...*/ ]
  }
}

<DataSource<Developer>
  filterTypes={filterTypes}

Note

When you specify new filterTypes, the default filter types of string and number are still available - unless the new object contains those keys and overrides them explicitly.

Client-side filtering in action with custom filter type

The salary column has a custom filter type, with the following operators: gt, gte, lt and lte.

View Mode
Fork
import * as React from 'react';

import {
  DataSourceData,
  DataSource,
  InfiniteTable,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    defaultFilterable: true,
    field: 'salary',
    type: 'number',
    filterType: 'salary',
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
  currency: { field: 'currency', defaultFilterable: false },
};

function getIcon(icon: string) {
  return () => (
    <div
      style={{
        width: 20,
        display: 'flex',
        justifyContent: 'center',
        flexFlow: 'row',
      }}
    >
      {icon}
    </div>
  );
}

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
          filterTypes={{
            salary: {
              defaultOperator: 'gt',
              emptyValues: ['', null, undefined],
              operators: [
                {
                  name: 'gt',
                  label: 'Greater Than',
                  components: {
                    Icon: getIcon('>'),
                  },
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue > filterValue;
                  },
                },
                {
                  name: 'gte',
                  components: {
                    Icon: getIcon('>='),
                  },
                  label: 'Greater Than or Equal',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue >= filterValue;
                  },
                },
                {
                  name: 'lt',
                  components: {
                    Icon: getIcon('<'),
                  },
                  label: 'Less Than',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue < filterValue;
                  },
                },
                {
                  name: 'lte',
                  components: {
                    Icon: getIcon('<='),
                  },
                  label: 'Less Than or Equal',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue <= filterValue;
                  },
                },
              ],
            },
          }}
        >
          <InfiniteTable<Developer>
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>

Customizing Default Filter Types

By default, the string and number filter types are available. You can import the default filter types like this:

import { defaultFilterTypes } from '@infinite-table/infinite-react';

If you want to make all your instances of InfiniteTable have new operators for those filter types, you can simply mutate the exported defaultFilterTypes object.

Enhanced string filter type - new 'Not includes' operator

The string columns have a new Not includes operator.

View Mode
Fork
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  defaultFilterTypes,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

defaultFilterTypes.string.operators.push({
  name: 'Not contains',
  label: 'Not Contains',
  fn: ({ currentValue, filterValue, emptyValues }) => {
    if (
      emptyValues.includes(currentValue) ||
      emptyValues.includes(filterValue)
    ) {
      return true;
    }
    return (
      typeof currentValue === 'string' &&
      typeof filterValue == 'string' &&
      !currentValue.toLowerCase().includes(filterValue.toLowerCase())
    );
  },
});

const data: DataSourceData<Developer> = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },

  salary: {
    field: 'salary',
    type: 'number',
  },
  currency: { field: 'currency', defaultFilterable: false },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
        >
          <InfiniteTable<Developer>
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>

Note

When you specify new filterTypes, the default filter types of string and number are still available - unless the new object contains those keys and override them explicitly.

Using a Filter Delay

In order to save some resources, filtering is batched by default. This is controlled by the filterDelay prop, which, if not specified, defaults to 200 milliseconds. This means, any changes to the column filters, that happen inside a 200ms window (or the current value of filterDelay), will be debounced and only the last value will be used to trigger a filter.

Note

If you want to prevent debouncing/batching filter values, you can set filterDelay to 0.

Note

API calls to setColumnFilter or clearColumnFilter are not batched.

Using a Filter Function Instead of the Column Filters

For client-side rendering, it's possible that instead of showing a column filter bar, you use a custom filterFunction to filter the data.

In this case, the filtering will happen client-side ... of course 🤦‍♂️.

Custom filterFunction example

Loads data from remote location but will only show rows that have id > 100.

View Mode
Fork
import * as React from 'react';

import {
  DataSourceData,
  DataSource,
  InfiniteTable,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    defaultFilterable: true,
    field: 'salary',
    type: 'number',
    filterType: 'salary',
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
  currency: { field: 'currency', defaultFilterable: false },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          filterFunction={({ data }) => {
            return data.id > 100;
          }}
        >
          <InfiniteTable<Developer>
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>