Table
We recommend using Tanstack’s Table for
your data and column model and pairing it with reablocks’ Pager
and Sort components for the chrome around it.
Tanstack handles the model (rows, sort state, pagination state); reablocks
handles the visuals.
Setup
npm install @tanstack/react-tableimport { useState } from 'react';
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type SortingState
} from '@tanstack/react-table';
import { Pager, Sort } from 'reablocks';
type User = { id: string; name: string; email: string; age: number };
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('email', { header: 'Email' }),
columnHelper.accessor('age', { header: 'Age' })
];Sortable headers with Sort
Sort is a presentational toggle — give it a direction of 'asc' | 'desc'
and a click handler. Wire those into Tanstack’s column-level sort API
(column.getIsSorted() / column.toggleSorting()) and the table model takes
care of the rest.
import { Sort } from 'reablocks';
function SortableHeader({ column, children }) {
const sorted = column.getIsSorted(); // 'asc' | 'desc' | false
if (!column.getCanSort()) {
return <>{children}</>;
}
return (
<Sort
direction={sorted === false ? 'asc' : sorted}
onSort={() => column.toggleSorting()}
>
{children}
</Sort>
);
}Then render it inside the header cell:
<thead>
{table.getHeaderGroups().map(group => (
<tr key={group.id}>
{group.headers.map(header => (
<th key={header.id} className="text-left p-2">
<SortableHeader column={header.column}>
{flexRender(header.column.columnDef.header, header.getContext())}
</SortableHeader>
</th>
))}
</tr>
))}
</thead>Pagination with Pager
Pager is fully controlled and takes page (zero-indexed), size, total,
and onPageChange. Drive it from Tanstack’s pagination state:
<Pager
page={table.getState().pagination.pageIndex}
size={table.getState().pagination.pageSize}
total={data.length}
onPageChange={pageIndex => table.setPageIndex(pageIndex)}
displayMode="all"
/>For server-driven data, swap data.length for the total returned by your
API and set manualPagination: true on the table — onPageChange then
becomes the trigger for your fetch.
Putting it all together
import { useState } from 'react';
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
type SortingState
} from '@tanstack/react-table';
import { Pager, Sort } from 'reablocks';
type User = { id: string; name: string; email: string; age: number };
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('email', { header: 'Email' }),
columnHelper.accessor('age', { header: 'Age' })
];
export const UserTable = ({ data }: { data: User[] }) => {
const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
const table = useReactTable({
data,
columns,
state: { sorting, pagination },
onSortingChange: setSorting,
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel()
});
return (
<div className="flex flex-col gap-4">
<table className="w-full border-collapse">
<thead>
{table.getHeaderGroups().map(group => (
<tr key={group.id} className="border-b border-panel-accent">
{group.headers.map(header => {
const sorted = header.column.getIsSorted();
const canSort = header.column.getCanSort();
const label = flexRender(
header.column.columnDef.header,
header.getContext()
);
return (
<th key={header.id} className="text-left p-2">
{canSort ? (
<Sort
direction={sorted === false ? 'asc' : sorted}
onSort={() => header.column.toggleSorting()}
>
{label}
</Sort>
) : (
label
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id} className="border-b border-panel-accent">
{row.getVisibleCells().map(cell => (
<td key={cell.id} className="p-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<div className="flex justify-end">
<Pager
page={table.getState().pagination.pageIndex}
size={table.getState().pagination.pageSize}
total={data.length}
onPageChange={pageIndex => table.setPageIndex(pageIndex)}
displayMode="all"
/>
</div>
</div>
);
};Notes
- Zero-indexed pages. Both Tanstack Table and
Pageruse a zero-indexedpageIndex/page, so they pass through cleanly without arithmetic. - Multi-column sort.
Sortonly models a single direction. If you need multi-column sorting, render multipleSorttoggles and let Tanstack’ssortingstate hold the array — only the active column’sSortwill show a non-default direction. - Server-driven data. Set
manualSorting: trueandmanualPagination: trueon the table and re-fetch in youronSortingChange/onPaginationChangehandlers.Pager’stotalshould be the server’s total count, notdata.length.
More
Pager— full prop reference and theme.Sort— full prop reference and theme.- Tanstack Table — the underlying table model.