Richard Beattie

React Table useGlobalFilter with two columns

This week I needed to filter a table made with React Table. React table is great for its extensibility and so has hooks for this: useFilters and useGlobalFilter. useFilter is for filtering by an individual column and useGlobalFilter for filtering by content in any column.

However I needed to filter two columns by a single input. My table is for ingredients and has columns for: code, name, price per unit, supplier, etc. I need to filter by name and code. I couldn’t use useFilters as that would do an intersection (i.e. the search query would need to be in both the code and name row).

See the end Code Sandbox at: https://codesandbox.io/s/fuzzy-text-global-filter-rcy1f?file=/src/Table.tsx

Prepsheets Screenshot

Filtering Prepsheets.com Ingredients by Code

Thankfully, it’s possible to do this with useGlobalFilter.

import {
	...,
	useGlobalFilter,
} from 'react-table'

...

const {
	...,
	setGlobalFilter,
} = useTable(
	{
...
	},
	...
	useGlobalFilter,
);

See Code Sandbox: https://codesandbox.io/s/all-columns-global-filter-buof9?file=/src/Table.tsx

Awesome, by using setGlobalFilter we can now filter by the content in any column. To restrict which columns we filter we’ll specify a custom globalFilter function.

import {
	...
	useGlobalFilter,
	Row, // Typescript
	IdType, // Typescript
}
import React, { ..., useCallback } = 'react'

...

const ourGlobalFilterFunction = useCallback(
	// This is Typescript if you're using JS remove the types (e.g. :string)
	(rows: Row<T>[], ids: IdType<T>[], query: string) => {
return rows.filter((row) =>
	row.values['code'].includes(query) ||
	row.values['name'].includes(query)
);
	},
	[],
);

const {
    ...
    setGlobalFilter,
} = useTable(
	{
globalFilter: ourGlobalFilterFunction
	},
	...
	useGlobalFilter,
);

See Code Sandbox: https://codesandbox.io/s/specific-columns-global-filter-n1k4v?file=/src/Table.tsx

However, in my case this is inside a Table component which I use in a couple of places, so let’s make ourGlobalFilterFunction take arbitrary column names for filtering. We’ll also pass the filter query as a prop to Table.

interface TableProps {
	filters: string[];
	filter: string;
}

const Table: React.FC<TableProps> = ({
	filters,
	filter,
}): React.ReactComponent => {

	const ourGlobalFilterFunction = useCallback(
// This is Typescript if you're using JS remove the types (e.g. :string)
(rows: Row<T>[], ids: IdType<T>[], query: string) => {
	return rows.filter((row) =>
		for (const filter of filters) {
			return row.values[filter].includes(query)
		}
	);
},
[filters],
	);

	const {
	  ...
	  setGlobalFilter,
	} = useTable(
{
	globalFilter: ourGlobalFilterFunction
},
...
useGlobalFilter,
	);

	useEffect(() => {
setGlobalFilter(filter) // Set the Global Filter to the filter prop.
	}, [filter, setGlobalFilter]);

	return (
...
	);
}

Code Sandbox: https://codesandbox.io/s/filter-props-global-filter-i18bd?file=/src/Table.tsx

Finally, I’d like this to do fuzzy text filtering. We’ll use the match-sorter library for this

npm install match-sorter
import { matchSorter } from 'match-sorter';

...

const globalFilter = useCallback(
	(rows: Row<T>[], ids: IdType<T>[], query: string) => {
return matchSorter(rows, query, {
	keys: filters.map((columnName) => `values.${columnName}`),
});
	},
	[filters],
);

Code Sandbox: https://codesandbox.io/s/fuzzy-text-global-filter-rcy1f?file=/src/Table.tsx