Capítulo 5 de 54

Chapter 5: Quick Start (React)

Core Idea

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.

Key Concepts

  • 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.
  • Columns are typed against the features object (ColumnDef<typeof features, Person>), so TypeScript only exposes column options that are valid for the features actually registered.
  • Two ways to point a column at data: 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.
  • Adding a feature (e.g. sorting) is always the same shape: register the feature flag + its row model factory (if any) in tableFeatures, then use the APIs it adds — header.column.getCanSort(), getToggleSortingHandler(), getIsSorted() for sorting specifically.
  • Table state in v9 is backed by TanStack Store atoms internally — you rarely manage it by hand. Use 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).
  • When multiple tables in an app share the same features/row models/component conventions, define them once via createTableHook({ features }) instead of repeating tableFeatures/useTable per table (see Composable Tables Guide).

Code Examples

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>
  )
}
  • What it demonstrates: the complete minimal shape every table in this library follows — data → features → columns → useTable → hand-written markup.

Worked Example

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.

Key Takeaways

  1. Start with tableFeatures({}) (nothing enabled) and add features one at a time as you need them — this is what keeps the bundle tree-shaken.
  2. accessorKey for direct fields, accessorFn + explicit id for computed/derived columns.
  3. Always render through table.FlexRender, never by manually checking typeof cell.column.columnDef.cell.
  4. Don't fight v9's internal state management — use initialState + feature setters first, and only lift state out when you have a concrete reason (URL sync, server-driven state, shared state across components).

Connects To

  • Migrating to TanStack Table V9 (React): what changes if you're coming from v8's useReactTable.
  • Table State (React) Guide: how state actually works under the hood (TanStack Store atoms).
  • Composable Tables (createTableHook) Guide: sharing one tableFeatures config across many tables.
  • Sorting (React) Guide: the full sorting feature this chapter previews.