# Client-side Filtering

> Learn how to configure client-side filtering for your Infinite Table React DataGrid

Canonical page: https://infinite-table.com/docs/learn/filtering/filtering-client-side

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`](https://infinite-table.com/docs/reference/datasource-props/index.md#data) property is a function (and not an array or a `Promise`), then the filtering will happen server-side by default.

To force client-side filtering, you can explicitly set the [filterMode="local"](https://infinite-table.com/docs/reference/datasource-props/index.md#filterMode) 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`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterValue) will be passed as a property to the parameter object sent to the [`data`](https://infinite-table.com/docs/reference/datasource-props/index.md#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`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultFilterValue) property or the controlled [`filterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterValue) version.

**Example: 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.

```ts
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(process.env.NEXT_PUBLIC_BASE_URL + `/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>
            debugId="basic-local-filter-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

If you still want filtering to be enabled with the default functionality of using the [`filterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterValue) (or uncontrolled [`defaultFilterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultFilterValue)), but want to hide the column filter editors, you can set the [showColumnFilters](https://infinite-table.com/docs/reference/datasource-props/index.md#showColumnFilters) property to `false`.

## Using Filter Types

As already documented in the [Understanding Filter Types](./#understanding-filter-types) section, you can specify the types of the filters the `<DataSource />` will support, by using the [`filterTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#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.

```tsx {5,14} title="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`](https://infinite-table.com/docs/reference/datasource-props/index.md#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:

```tsx
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:

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

<DataSource<Developer>
  filterTypes={filterTypes}
/>
```

When you specify new [`filterTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterTypes), the default filter types of `string` and `number` are still available - unless the new object contains those keys and overrides them explicitly.

**Example: 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`.

```ts
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(process.env.NEXT_PUBLIC_BASE_URL + `/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>
            debugId="filter-custom-filter-type-example"
            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:

```ts
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.

**Example: Enhanced string filter type - new 'Not includes' operator**

The `string` columns have a new `Not includes` operator.

```ts
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(process.env.NEXT_PUBLIC_BASE_URL + `/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>
            debugId="customised-default-filter-types-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

When you specify new [`filterTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#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`](https://infinite-table.com/docs/reference/datasource-props/index.md#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`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterDelay)), will be debounced and only the last value will be used to trigger a filter.

If you want to prevent debouncing/batching filter values, you can set [`filterDelay`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterDelay) to `0`.

API calls to [`setColumnFilter`](https://infinite-table.com/docs/reference/api/index.md#setColumnFilter) or [`clearColumnFilter`](https://infinite-table.com/docs/reference/api/index.md#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`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterFunction) to filter the data.

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

**Example: Custom filterFunction example**

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

```ts
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(process.env.NEXT_PUBLIC_BASE_URL + `/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>
            debugId="filter-function-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```
