Capítulo 5 de 54
A working v9 table needs exactly five ingredients: typed data, a tableFeatures({...}) declaration of which optional features you're opting into, ColumnDefs typed against those features, useTable({ key, features, columns, data }), and markup you render yourself from table.getHeaderGroups() / table.getRowModel().rows.
tableFeatures({}) is new in v9 — it explicitly declares which optional features (sorting, filtering, pagination, ...) a table uses. An empty object means core-only: no opt-in features, smallest bundle. The core row model is always included; feature row models (e.g. sorted, filtered, paginated) are registered as slots inside tableFeatures.features object (ColumnDef<typeof features, Person>), so TypeScript only exposes column options that are valid for the features actually registered.accessorKey: 'firstName' (shorthand for a direct property) or accessorFn: (row) => ... + an explicit id (for derived/computed values).table.FlexRender renders a column's header/cell/footer definition, whether it's a plain string, a function, or a component — use it instead of manually branching on the definition's type.key on useTable is optional unless you use Devtools; when present it's also how useTanStackTableDevtools(table) finds the table.tableFeatures, then use the APIs it adds — header.column.getCanSort(), getToggleSortingHandler(), getIsSorted() for sorting specifically.initialState for starting values and feature setters (table.setSorting(...), table.nextPage()) for imperative changes; only reach for external state control when your app needs to own a slice or needs fine-grained subscriptions (see Table State Guide).createTableHook({ features }) instead of repeating tableFeatures/useTable per table (see Composable Tables Guide).import { tableFeatures, useTable } from '@tanstack/react-table'
import type { ColumnDef } from '@tanstack/react-table'
type Person = { firstName: string; lastName: string; age: number }
const data: Person[] = [{ firstName: 'Ada', lastName: 'Lovelace', age: 36 }]
const features = tableFeatures({}) // no optional features yet
const columns: ColumnDef<typeof features, Person>[] = [
{ accessorKey: 'firstName', header: 'First Name', cell: (info) => info.getValue() },
{ accessorFn: (row) => row.lastName, id: 'lastName', header: 'Last Name' },
]
function PersonTable() {
const table = useTable({ key: 'person-table', features, columns, data })
return (
<table>
<thead>
{table.getHeaderGroups().map((hg) => (
<tr key={hg.id}>
{hg.headers.map((h) => (
<th key={h.id}>{h.isPlaceholder ? null : <table.FlexRender header={h} />}</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getAllCells().map((cell) => (
<td key={cell.id}><table.FlexRender cell={cell} /></td>
))}
</tr>
))}
</tbody>
</table>
)
}
useTable → hand-written markup.Turning the table above sortable requires only registering the sorting feature and wiring one click handler — no new component, no prop-drilling of sort state:
import { createSortedRowModel, rowSortingFeature, sortFns, tableFeatures, useTable } from '@tanstack/react-table'
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns,
})
// in the header cell: onClick={header.column.getToggleSortingHandler()}
// and read header.column.getIsSorted() to render an asc/desc indicator
This is the general pattern for every feature in the library: add the feature flag (+ row model factory, if it has one) to tableFeatures, then consume the getters/setters it adds to table/column/row/header/cell.
tableFeatures({}) (nothing enabled) and add features one at a time as you need them — this is what keeps the bundle tree-shaken.accessorKey for direct fields, accessorFn + explicit id for computed/derived columns.table.FlexRender, never by manually checking typeof cell.column.columnDef.cell.initialState + feature setters first, and only lift state out when you have a concrete reason (URL sync, server-driven state, shared state across components).useReactTable.tableFeatures config across many tables.