React Table
refine offers a TanStack Table adapter with @pankod/refine-react-table that allows you to use the TanStack Table library with refine. Thus, you can manage your server-side data fetching operations.
All of TanStack Table's features are supported and you can use all of the TanStack Table's examples with no changes just copy and paste them into your project.
Installation
Install the @pankod/refine-react-table
library.
npm i @pankod/refine-react-table
Basic Usage
In this documentation, we'll step-by-step create an example of a headless table with sorting, filtering, and pagination capabilities.
Let's say you have a endpoint that returns the following data.
[
{
"id": 182,
"title": "A aspernatur rerum molestiae.",
"content": "Natus molestias incidunt voluptatibus. Libero delectus facilis...",
"status": "published",
"createdAt": "2021-04-18T00:09:11.607Z"
},
{
"id": 989,
"title": "A molestiae vel voluptatem enim.",
"content": "Voluptas consequatur quia beatae. Ipsa est qui culpa deleniti...",
"status": "draft",
"createdAt": "2020-01-28T02:57:58.892Z"
}
]
Create <PostList>
component
We simply create a <PostList>
component and pass to the <Refine>
component as a resource. All the implementation we will do from now on will be in the <PostList>
component.
export const PostList: React.FC = () => {
return <></>;
};
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-react-router-v6";
import dataProvider from "@pankod/refine-simple-rest";
import "./App.css";
import { PostList } from "pages/posts/list";
const App: React.FC = () => {
return (
<Refine
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
routerProvider={routerProvider}
resources={[{ name: "posts", list: PostList }]}
/>
);
};
export default App;
Show basic table style
table {
border-spacing: 0;
border: 1px solid black;
}
table th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
}
table tr:last-child td {
border-bottom: 0;
}
table th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
}
table th:last-child,
td:last-child {
border-right: 0;
}
Create basic table
Firts, we need to import the useTable
hook from the @pankod/refine-react-table
library.
import { useTable } from "@pankod/refine-react-table";
export const PostList: React.FC = () => {
return <></>;
};
Define columns what we want to display in the table. Then, return the headless table with using the useTable
hook.
useTable
does not expect any data prop to be passed to it. It will fetch data from the data provider by resource.
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
interface IPost {
id: number;
title: string;
status: "published" | "draft" | "rejected";
createdAt: string;
}
export const PostList: React.FC = () => {
const columns = React.useMemo<ColumnDef<IPost>[]>(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "title",
header: "Title",
accessorKey: "title",
},
{
id: "status",
header: "Status",
accessorKey: "status",
},
{
id: "createdAt",
header: "CreatedAt",
accessorKey: "createdAt",
},
],
[],
);
const { getHeaderGroups, getRowModel } = useTable({ columns });
return (
<table>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
This example is the same as the basic example in the TanStack Table documentation.

Pagination
TanStack Table provides a bunch of methods that we can use to control the pagination. For example, we can use the setPageSize
method to set the current pageSize
. Every change in the pageSize
and pageIndex
will trigger a new request to the data provider.
Refer to the TanStack Table Pagination API documentation for detailed information. →
useTable
hook from @pankod/refine-react-table
sets manualPagination
to true
by default to handle the pagination. If you set hasPagination
to false
in refineCoreProps
property in the useTable
config, it will disable the server-side pagination and it will let you handle the pagination in the client side.
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
interface IPost {
id: number;
title: string;
status: "published" | "draft" | "rejected";
createdAt: string;
}
export const PostList: React.FC = () => {
const columns = React.useMemo<ColumnDef<IPost>[]>(...); // Defined in the previous section
const {
getHeaderGroups,
getRowModel,
getState,
setPageIndex,
getCanPreviousPage,
getPageCount,
getCanNextPage,
nextPage,
previousPage,
setPageSize,
} = useTable({ columns });
return (
<>
<table>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))}
</tbody>
</table>
// Pagination can be built however you'd like. // This is just a very
basic UI implementation: //highlight-start
<div>
<button
onClick={() => setPageIndex(0)}
disabled={!getCanPreviousPage()}
>
{"<<"}
</button>
<button
onClick={() => previousPage()}
disabled={!getCanPreviousPage()}
>
{"<"}
</button>
<button onClick={() => nextPage()} disabled={!getCanNextPage()}>
{">"}
</button>
<button
onClick={() => setPageIndex(getPageCount() - 1)}
disabled={!getCanNextPage()}
>
{">>"}
</button>
<span>
<div>Page</div>
<strong>
{getState().pagination.pageIndex + 1} of{" "}
{getPageCount()}
</strong>
</span>
<span>
| Go to page:
<input
type="number"
defaultValue={getState().pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value
? Number(e.target.value) - 1
: 0;
setPageIndex(page);
}}
/>
</span>
<select
value={getState().pagination.pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
);
};

Sorting
TanStack Table provides a bunch of methods that we can use to control the sorting. For example, we can use the setColumnOrder
method to set the current sorting
value. Every change in the sorting
state will trigger a new request to the data provider.
Refer to the useSortBy
documentation for detailed information. →
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
interface IPost {
id: number;
title: string;
status: "published" | "draft" | "rejected";
createdAt: string;
}
export const PostList: React.FC = () => {
const columns = React.useMemo<ColumnDef<IPost>[]>(...); // Defined in the previous section
const {
getHeaderGroups,
getRowModel,
getState,
setPageIndex,
getCanPreviousPage,
getPageCount,
getCanNextPage,
nextPage,
previousPage,
setPageSize,
} = useTable({ columns });
return (
<>
<table>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<div
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: " 🔼",
desc: " 🔽",
}[
header.column.getIsSorted() as string
] ?? null}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))}
</tbody>
</table>
// Pagination defined in the previous section
</>
);
};

Filtering
TanStack Table provides a bunch of methods that we can use to control the filtering. For example, we can use the setColumnFilters
method to set the current columnFilters
value. Every change in the filter
will trigger a new request to the data provider.
You can specify which field will be filtered with which filter operator with the filterOperator
property in the meta
object. filterOperator
must be a CrudOperators
type.
import { useTable, ColumnDef, flexRender } from "@pankod/refine-react-table";
interface IPost {
id: number;
title: string;
status: "published" | "draft" | "rejected";
createdAt: string;
}
export const PostList: React.FC = () => {
const columns = React.useMemo<ColumnDef<IPost>[]>(
() => [
{
id: "id",
header: "ID",
accessorKey: "id",
},
{
id: "title",
header: "Title",
accessorKey: "title",
meta: {
filterOperator: "contains",
},
},
{
id: "status",
header: "Status",
accessorKey: "status",
meta: {
filterOperator: "contains",
},
},
{
id: "createdAt",
header: "CreatedAt",
accessorKey: "createdAt",
},
],
[],
);
const {
getHeaderGroups,
getRowModel,
getState,
setPageIndex,
getCanPreviousPage,
getPageCount,
getCanNextPage,
nextPage,
previousPage,
setPageSize,
} = useTable({ columns });
return (
<>
<table>
<thead>
{getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef
.header,
header.getContext(),
)}
{{
asc: " 🔼",
desc: " 🔽",
}[
header.column.getIsSorted() as string
] ?? null}
</div>
<div>
<input
value={
(header.column.getFilterValue() as string) ??
""
}
onChange={(e) =>
header.column.setFilterValue(
e.target.value,
)
}
/>
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
))}
</tbody>
</table>
// Pagination defined in the previous section
</>
);
};

API Reference
Properties
It also accepts all props of TanStack Table.
Type Parameters
Property | Desription | Type | Default |
---|---|---|---|
TData | Result data of the query. Extends BaseRecord | BaseRecord | BaseRecord |
TError | Custom error object that extends HttpError | HttpError | HttpError |
Return values
Property | Description | Type |
---|---|---|
refineCore | The return values of the useTable in the core | UseTableReturnValues |
Tanstack Table Return Values | See TanStack Table documentation |