Skip to main content
πŸ§™β€β™‚οΈ refine grants your wishes! Please give us a ⭐️ on GitHub to keep the magic going.
Version: 4.xx.xx

6. Adding Sort and Filters

Sort and Filters​

The @refinedev/react-table package is based on the TanStack Table package, meaning that we can add sorting and filtering features to our table as suggested in the TanStack documentation.

Tanstack Table keeps the sorting and filters states in the useTable hook. When these states are changed, the useTable hook will automatically fetch the data and update the table with the new data.

INFORMATION

Under the hood, sortingΒ and filters states of Tanstack Table are converted to the CrudSorting and CrudFilter types of refine. So, when you change the Tanstack Table's sorting or filters state, useTable hook will pass the converted params to the getList method of the dataProvider.

Since @refinedev/react-table provides a headless solution, there are many ways to handle filtering and sorting. In this tutorial, we will show a basic way of adding sorting and filtering to the table.

Adding Sorting​

We first need to create a ColumnSorter/> component to use in our table header, which, when clicked on, will sort the table by the column:

src/components/table/ColumnSorter.tsx
import { IconButton } from "@chakra-ui/react";
import { IconChevronDown, IconSelector, IconChevronUp } from "@tabler/icons";
import type { Column } from "@tanstack/react-table";

export const ColumnSorter: React.FC<{ column: Column<any, any> }> = ({
column,
}) => {
if (!column.getCanSort()) {
return null;
}

const sorted = column.getIsSorted();

return (
<IconButton
aria-label="Sort"
size="xs"
onClick={column.getToggleSortingHandler()}
>
{!sorted && <IconSelector size={18} />}
{sorted === "asc" && <IconChevronDown size={18} />}
{sorted === "desc" && <IconChevronUp size={18} />}
</IconButton>
);
};

<ColumnSorter/> is a simple component that renders a button, which will call the column.getToggleSortingHandler() method that will toggle the sorting state of the table when clicked on.

In addition, we are using the column.getCanSort() method to check if the column is sortable and not render the <ColumnSorter/> if it is not.

Lastly, if the column is not sorted, the IconSelector component is displayed; otherwise, either the IconChevronDown or IconChevronUp component is displayed based on the current sorting state.

TIP

In this example, we are using the @tabler/icons package for icons but you can use any icon library you want.

Now, we can finally use <ColumnSorter/> in our table header.

First, import the <ColumnSorter/> component:

```tsx title="src/pages/blog-posts/list.tsx"
import { ColumnSorter } from "../../components/table/ColumnSorter";
```

Then add the <ColumnSorter/> component to the <Th/> as a child like below:

```tsx title="src/pages/blog-posts/list.tsx"
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
{!header.isPlaceholder &&
flexRender(
header.column.columnDef.header,
header.getContext(),
)}
//highlight-next-line
<ColumnSorter column={header.column} />
</Th>
))}
</Tr>
))}
</Thead>
```

Finally, disable sorting for the actions column by setting the enableSorting property of the column to false in the column definition like below:

```tsx title="src/pages/blog-posts/list.tsx"
{
id: "actions",
accessorKey: "id",
header: "Actions",
//highlight-next-line
enableSorting: false,
},
```

Now, we can sort the table by clicking on the sort button in the table header.

TIP

If you want to disable sorting for a specific column, you can set the enableSorting property of the column to false in the column definition:

{
id: "name",
accessorKey: "name",
header: "Name",
enableSorting: false,
},

Adding Filters​

Create a <ColumnFilter/> component to use in our table header which will be responsible for changing the filters state of the table.

src/components/table/ColumnFilter.tsx
import React, { useState } from "react";
import {
Input,
Menu,
IconButton,
MenuButton,
MenuList,
VStack,
HStack,
} from "@chakra-ui/react";
import { IconFilter, IconX, IconCheck } from "@tabler/icons";
import type { Column } from "@tanstack/react-table";

export const ColumnFilter: React.FC<{ column: Column<any, any> }> = ({
column,
}) => {
const [state, setState] = useState(null as null | { value: any });

if (!column.getCanFilter()) {
return null;
}

const open = () =>
setState({
value: column.getFilterValue(),
});

const close = () => setState(null);

const change = (value: any) => setState({ value });

const clear = () => {
column.setFilterValue(undefined);
close();
};

const save = () => {
if (!state) return;
column.setFilterValue(state.value);
close();
};

const renderFilterElement = () => {
const FilterComponent = (column.columnDef?.meta as any)?.filterElement;

if (!FilterComponent && !!state) {
return (
<Input
borderRadius="md"
size="sm"
autoComplete="off"
value={state.value}
onChange={(e) => change(e.target.value)}
/>
);
}

return (
<FilterComponent
value={state?.value}
onChange={(e: any) => change(e.target.value)}
/>
);
};

return (
<Menu isOpen={!!state} onClose={close}>
<MenuButton
onClick={open}
as={IconButton}
aria-label="Options"
icon={<IconFilter size="16" />}
variant="ghost"
size="xs"
/>
<MenuList p="2">
{!!state && (
<VStack align="flex-start">
{renderFilterElement()}
<HStack spacing="1">
<IconButton
aria-label="Clear"
size="sm"
colorScheme="red"
onClick={clear}
>
<IconX size={18} />
</IconButton>
<IconButton
aria-label="Save"
size="sm"
onClick={save}
colorScheme="green"
>
<IconCheck size={18} />
</IconButton>
</HStack>
</VStack>
)}
</MenuList>
</Menu>
);
};

<ColumnFilter/> is a component that renders a button which will open a menu when clicked on. In the menu, we are rendering the filter element of the column, which is <Input/> in this example but you can render any component you want.

Filter element is a component that renders an input element. When the user changes the value of the input element, the filter value of the column will be changed.

<ColumnFilter/> also contains "clear" and "apply" buttons, which will respectively clear or set the filter value of the column when clicked on.

Now, we can use <ColumnFilter/> in our table header.

  1. Import the <ColumnFilter/> component:

    src/pages/blog-posts/list.tsx
    import { ColumnFilter } from "../../components/table/ColumnFilter";
  2. Add the <ColumnFilter/> component to the <Th/> as a child:

    src/pages/blog-posts/list.tsx
    <Thead>
    {getHeaderGroups().map((headerGroup) => (
    <Tr key={headerGroup.id}>
    {headerGroup.headers.map((header) => (
    <Th key={header.id}>
    {!header.isPlaceholder &&
    flexRender(
    header.column.columnDef.header,
    header.getContext(),
    )}
    <ColumnSorter column={header.column} />
    <ColumnFilter column={header.column} />
    </Th>
    ))}
    </Tr>
    ))}
    </Thead>
  3. Change the filter operator for columns to "contains" by changing the meta property of the column definition:

    {
    id: "title",
    accessorKey: "title",
    header: "Title",
    meta: {
    filterOperator: "contains",
    },
    },
    {
    id: "content",
    accessorKey: "content",
    header: "Content",
    meta: {
    filterOperator: "contains",
    },
    },
    TIP

    There are many values that you can pass to the filterOperator, for more information about them, refer to the Filtering section of the useTable documentation→

  4. Disable filtering for the "actions" column by setting the enableColumnFilter property of the column to false in the column definition like below:

    {
    id: "actions",
    accessorKey: "id",
    header: "Actions",
    enableColumnFilter: false,
    ...
    },
    TIP

    You can similarly disable filtering for specific columns by setting their enableColumnFilter property to false.

Now, we can filter the table by clicking on the filter button in the table header.

How can I use a custom filter element?

If you want to use a custom filter element, you can pass it to the filterElement property of the meta in column definition. For example, you can pass a <Select/> component like below:

{
id: "category",
header: "Category",
accessorKey: "category.id",
meta: {
filterElement: (props) => <Select {...props} />,
},
},

In the props, the filter element will receive the value and onChange props. You can use these props to change the filter value of the column.


Checklist