OMAR
Field NotesCV
TanStack Table: Stop Rewriting Sorting and Pagination
← All Notes
Frontend03 August 2026 · 3 min read

TanStack Table: Stop Rewriting Sorting and Pagination

It renders nothing. That is the point — you get sorting, filtering, grouping and pagination as logic, and the markup stays yours.

Every admin dashboard needs a table, and every admin dashboard grows the same four features in the same order: sorting, then filtering, then pagination, then column visibility. I have written that four times from scratch, and the fourth one still had a bug where sorting reset the page to a page that no longer existed.

The problem is that full-featured table components solve it by owning the markup, which means fighting their CSS forever. TanStack Table solves it by owning none of the markup.

Headless means it renders nothing

const columns = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'email', header: 'Email' },
  { accessorKey: 'createdAt', header: 'Joined',
    cell: (info) => new Date(info.getValue()).toLocaleDateString() },
]

const table = useReactTable({
  data, columns,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
})

table is now an object describing rows, headers, sort state and page state. You write the <table> yourself:

<tbody>
  {table.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>

Your classes, your semantics, your responsive behaviour. On the projects where I need a card layout on mobile and a table on desktop, that is the difference between a morning and a week.

Column definitions as documentation

The column array ends up being the clearest description of a screen in the codebase: what is shown, where the value comes from, how it is formatted, whether it sorts. New developers read it before they read the component.

Server-side when the data outgrows the browser

Ten thousand rows should not be sent to a phone. Switch the row models to manual and let the API do the work:

useReactTable({
  data, columns,
  manualPagination: true,
  manualSorting: true,
  rowCount: total,
})

The component API does not change — only where the sorting happens. That means the migration from client-side to server-side is a config change rather than a rewrite, which is exactly the migration everyone puts off until it is painful.

The bilingual note

Because you own the markup, an RTL table is just your markup in RTL. Header alignment, numeral formatting and the sort-arrow direction stay under your control instead of being baked into a vendor stylesheet you have to override with !important.

When not to bother

A five-row static list is a <table>. Reach for this when the table has behaviour, not when it has rows.

Resources

ReactDataTanStack

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading