TanStack Table v9 for the octane UI framework.
TanStack Table v9 separates a framework-agnostic core (@tanstack/table-core:
constructTable plus tree-shakeable features — sorting, filtering, pagination,
selection, visibility, expanding, grouping, faceting, …) from a thin framework
adapter. This package reuses the core unchanged (re-exported verbatim) and ports
the adapter onto octane. The public surface matches @tanstack/react-table v9
1:1, so code ported from React works by changing the import.
import {
createSortedRowModel,
flexRender,
rowSortingFeature,
sortFns,
tableFeatures,
useTable,
} from '@octanejs/tanstack-table';
// v9: opt into exactly the features you use — the rest is tree-shaken away.
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns,
});
function People() @{
const table = useTable({ features, data, columns });
<table>
<thead>
@for (const hg of table.getHeaderGroups(); key hg.id) {
<tr>
@for (const header of hg.headers; key header.id) {
<th onClick={header.column.getToggleSortingHandler()}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</th>
}
</tr>
}
</thead>
<tbody>
@for (const row of table.getRowModel().rows; key row.id) {
<tr>
@for (const cell of row.getAllCells(); key cell.id) {
<td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
}
</tr>
}
</tbody>
</table>
}| import | what you get |
|---|---|
@octanejs/tanstack-table |
everything @tanstack/table-core exports + useTable, Subscribe, flexRender, FlexRender, createTableHook, createTableHookContexts |
@octanejs/tanstack-table/flex-render |
flexRender / FlexRender alone |
@octanejs/tanstack-table/static-functions |
table-core's static function surface |
@octanejs/tanstack-table/experimental-worker-plugin |
table-core's experimental worker plugin |
v9 keeps every state slice in a TanStack Store atom. useTable supplies octane's
reactivity bindings to table-core, then subscribes with useSelector:
// Subscribe to everything (default).
const table = useTable({ features, data, columns });
table.state.sorting;
// Or project, and only re-render when the projection changes (shallow compare).
const table = useTable({ features, data, columns }, (state) => ({
pagination: state.pagination,
}));
table.state.pagination;For finer-grained updates, keep the component subscribed to little or nothing and
opt in lower down with table.Subscribe:
<table.Subscribe selector={(state) => state.rowSelection}>
{(rowSelection) => <span>{Object.keys(rowSelection).length as unknown as string}</span>}
</table.Subscribe>
// Or subscribe to a single atom directly.
<table.Subscribe source={table.atoms.rowSelection}>
{(rowSelection) => <span>…</span>}
</table.Subscribe>createTableHook is the table equivalent of TanStack Form's createFormHook:
declare features and reusable components once, then read the instance from
context inside them.
export const { useAppTable, createAppColumnHelper, useCellContext } = createTableHook({
features,
cellComponents: { TextCell },
tableComponents: { RowCount },
});
function TextCell() @{
const cell = useCellContext();
<span>{cell.getValue() as string}</span>
}
function UsersTable() @{
const table = useAppTable({ data, columns });
<table.AppTable>
<table.RowCount />
<table>
<tbody>
@for (const row of table.getRowModel().rows; key row.id) {
<tr>
@for (const c of row.getAllCells(); key c.id) {
<table.AppCell cell={c}>
{(cell) => <td><cell.TextCell /></td>}
</table.AppCell>
}
</tr>
}
</tbody>
</table>
</table.AppTable>
}useTable creates the table instance once and hands table-core a
coreReactivityFeature binding built on TanStack Store atoms (imported through
@octanejs/tanstack-store, which re-exports all of @tanstack/store), then
synchronizes options during every render so props (data, columns, controlled
state, feature handlers) land before any read. The returned object is
re-created each render so table.state and table.options are the values read
during that render; the underlying instance, its store, and its atoms are
stable for the component's lifetime.
flexRender triages a columnDef renderer: components render through octane's
createElement descriptor; strings, numbers, and pre-created elements pass
through as-is. Upstream's class-component and react.memo/forwardRef
exotic-object branches are dropped — octane has no class components or
forwardRef, and octane's memo() returns a plain function.
octane keys hooks by a compiler-injected per-call-site Symbol, appended as the
last argument of every use* call. Because useTable and useAppTable end in
an optional selector parameter, both split that trailing slot off their rest
args (see src/internal.ts) — otherwise useTable(options) would read the slot
symbol as the selector.
Upstream's useLegacyTable entry — the v8-compatibility shim with get*RowModel
marker factories and Legacy* type aliases — is deliberately absent. It exists to
migrate existing React v8 codebases; octane has none, so octane code targets the
v9 useTable API directly.
Current scope, known divergences, and verification status are tracked in the
generated bindings status table, sourced from
this package's status.json.