Skip to content
AdaptTable

Migrate from TanStack Table — headless, UI kits

▶ See it before you install: the live demo — the unstyled adapter with your own classes, plus seven kit-native ones in the switcher.

TanStack Table is a superb headless engine — you keep total control because it renders nothing: no markup, no toolbar, no filter inputs, no pagination controls, no URL sync. AdaptTable shares that philosophy. @adapttable/react is also headless (prop-getters, no forced markup), but it comes with the parts you rebuild on every TanStack project already wired: a filter UI, a toolbar, pagination, URL-synced state, and saved views. And when you don’t need bespoke markup, an adapter renders native kit components so you can delete the table UI entirely.

AdaptTable provides React, Angular and Vue bindings with native UI-kit adapters. This migration guide uses React. For the other bindings, see Angular setup and Vue setup.

Everything TanStack leaves to you (verified from its docs), done for you:

  • A filter UI. TanStack gives you columnFilters state and getFacetedUniqueValues, but you render every input. AdaptTable derives kit-native widgets, removable chips, and URL params from a filter declaration. See filtering.
  • A toolbar, pagination, and search — none of which exist in TanStack; you wire the buttons around nextPage()/setPageIndex() yourself.
  • URL-synced state. TanStack has no URL API; you serialize sorting/columnFilters/pagination by hand. AdaptTable does it. See URL state.
  • Saved views — no concept in TanStack. See saved views.
  • No opt-in row models. You don’t import getSortedRowModel / getFilteredRowModel / getPaginationRowModel — search, filter, sort, and paging run by default on the frontend tier.
  • Native kit UI, optional. Keep headless control with useDataTable prop-getters, or adopt an adapter (@adapttable/mantine, mui, …) and drop your hand-written markup.

The headless React binding only, or an adapter for ready UI:

Terminal window
# Headless (keep rendering your own markup, TanStack-style)
pnpm add @adapttable/core @adapttable/react
# Batteries — native components for your kit (delete the markup)
pnpm add @adapttable/core @adapttable/mantine @mantine/core @mantine/hooks
TanStack Table AdaptTable Notes
useReactTable({ data, columns, getCoreRowModel }) useDataTable({ source, columns, rowKey }) or an adapter <DataTable> source from useFrontendData; adapters accept data directly.
getSortedRowModel / getFilteredRowModel / getPaginationRowModel (none needed) Applied automatically on the frontend tier.
flexRender(cell.column.columnDef.cell, …) adapter renders, or prop-getters (getCellProps, …) Headless route mirrors TanStack’s model.
columnHelper.accessor("name", …) { key: "name" } Dot paths work in both.
columnHelper.accessor(row => …) (accessorFn) { key, accessor: (row) => … } —
columnHelper.display({ cell }) { key, Cell } Cell receives { row, rowIndex }.
header / cell / footer header / Cell (or accessor) / summaryRow Footer is one table-level function.
enableSorting / state.sorting / onSortingChange sortable per column (+ multiSort()) AdaptTable owns sort state.
columnFilters + getFilteredRowModel + your inputs column filter shorthand + filters([…]) Widgets + chips built for you.
state.pagination + getPaginationRowModel + your controls automatic pagination Paged on desktop, infinite on mobile.
enableRowSelection + state.rowSelection + your checkboxes bulkActions([…]) / selectedIds / onSelectionChange —
getExpandedRowModel + row.getToggleExpandedHandler() rowDetail(fn) Toggle UI is provided, not hand-added.
columnVisibility / columnOrder / columnPinning state columnMenu() / columnLayout One built-in menu instead of hand-wired state + DnD.
manualSorting / manualFiltering / manualPagination onQueryChange (or source via useQuerySource) One consolidated server query. See data tiers.
(serialize state to the URL yourself) urlSync / urlKey / savedViews({ storageKey }) URL sync is on by default; savedViews() is opt-in.

Before — TanStack: opt-in row models, flexRender, and hand-built pagination:

import {
createColumnHelper,
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
const col = createColumnHelper<Person>();
const columns = [
col.accessor("name", { header: "Name" }),
col.accessor("role", { header: "Role" }),
];
function PeopleTable({ people }: { people: Person[] }) {
const table = useReactTable({
data: people,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<>
<table>
<thead>
{table.getHeaderGroups().map((hg) => (
<tr key={hg.id}>
{hg.headers.map((h) => (
<th key={h.id} onClick={h.column.getToggleSortingHandler()}>
{flexRender(h.column.columnDef.header, h.getContext())}
</th>
))}
</tr>
))}
</thead>
<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>
</table>
{/* …and you still hand-write the pagination buttons, search box,
filter inputs, and URL syncing. */}
</>
);
}

After — an adapter renders native UI; search, sort, filters, pagination, and URL state come built in:

import { DataTable } from "@adapttable/mantine"; // or mui, chakra, antd, …
function PeopleTable({ people }: { people: Person[] }) {
return (
<DataTable
data={people}
rowKey={(r) => r.id}
columns={[
{ key: "name", sortable: true },
{ key: "role", filter: { type: "select", options: "auto" } },
]}
/>
);
}

Prefer to keep your own markup? Stay headless with @adapttable/react (walkthrough): useFrontendData for the source and useDataTable for the prop-getters (getTableProps, getHeaderCellProps, getSortButtonProps, getRowProps, getCellProps, getSearchInputProps) — the same headless shape as TanStack, but with filters, pagination, and URL state already handled.

  • Choose the matching binding. These examples use React. AdaptTable also supports Angular and Vue; it does not currently provide Svelte or Solid bindings.
  • rowKey is required (AdaptTable’s getRowId).
  • Delete the opt-in row models. No getSortedRowModel / getFilteredRowModel / getPaginationRowModel imports — those run for you.
  • Filters are declarative, not hand-rendered. Swap your custom filter inputs for a filter shorthand; a bespoke control can still be JSX in filters(<Form />).
  • You keep the headless escape hatch. useDataTable returns prop-getters, so moving to AdaptTable doesn’t mean giving up control — it means not rebuilding the chrome first.
  • Features compose in features. Import each factory from its kit subpath (@adapttable/mantine/row-detail, …). See feature composition.

If your project uses the legacy react-table package (v7 — useTable + useSortBy / useFilters / usePagination / useRowSelect plugin hooks), you’re on a frozen library: v7 stopped receiving releases when the project became TanStack Table v8. You have two upgrade paths, and both are rewrites of your table UI — the v7 plugin-hook API doesn’t carry over:

  • react-table v7 → TanStack v8: new package, new column defs, new row models — and you still hand-build every piece of UI afterwards.
  • react-table v7 → AdaptTable: the same rewrite cost, but you come out the other side with the toolbar, filters, pagination, URL sync, and native kit rendering already done.

The v7 concepts map cleanly: useTable({ columns, data }) → <DataTable data={…} columns={…}>; a v7 column’s Header/accessor/Cell → AdaptTable’s header/key (or accessor)/Cell; useSortBy → per-column sortable; usePagination → automatic; useRowSelect → bulkActions([…]) / onSelectionChange; useExpanded → rowDetail(fn). Since you must rewrite anyway, migrating “up” to batteries beats migrating sideways to another build-it-yourself engine.

Third-party notices