tanstack-table

TanStack Table best practices for building headless, type-safe data tables in React with sorting, filtering, pagination, row selection, and column management. Use when building data grids, implementing client-side or server-side table features, defining column structures, managing table state, or optimizing table rendering performance.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tanstack-table" with this command: npx skills add fellipeutaka/leon/fellipeutaka-leon-tanstack-table

TanStack Table

Version: @tanstack/react-table@latest Requires: React 16.8+, TypeScript recommended

Quick Setup

npm install @tanstack/react-table
import {
  useReactTable,
  getCoreRowModel,
  flexRender,
  createColumnHelper,
} from '@tanstack/react-table'

type User = {
  name: string
  age: number
  status: string
}

const columnHelper = createColumnHelper<User>()

const columns = [
  columnHelper.accessor('name', { header: 'Name' }),
  columnHelper.accessor('age', { header: 'Age' }),
  columnHelper.accessor('status', { header: 'Status' }),
  columnHelper.display({
    id: 'actions',
    cell: (props) => <button onClick={() => edit(props.row.original)}>Edit</button>,
  }),
]

function App() {
  const [data] = useState<User[]>([]) // must be stable reference

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

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((headerGroup) => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <th key={header.id}>
                {flexRender(header.column.columnDef.header, header.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>
  )
}

Row Models (Import Only What You Need)

TanStack Table is modular. Only import the row models you actually use:

import {
  getCoreRowModel,      // required
  getSortedRowModel,    // client-side sorting
  getFilteredRowModel,  // client-side filtering
  getPaginationRowModel,// client-side pagination
  getExpandedRowModel,  // expanding/sub-rows
  getGroupedRowModel,   // grouping + aggregation
  getFacetedRowModel,   // faceted values
  getFacetedUniqueValues,
  getFacetedMinMaxValues,
} from '@tanstack/react-table'

Pipeline order: Core -> Filtered -> Grouped -> Sorted -> Expanded -> Paginated -> Rendered

Rule Categories

PriorityCategoryRule FileImpact
CRITICALTable Setuprules/table-setup.mdCorrect table creation, stable data references
CRITICALColumn Definitionsrules/col-column-defs.mdData model, rendering, type safety
CRITICALRow Modelsrules/rm-row-models.mdModular imports, pipeline order
HIGHSortingrules/sort-sorting.mdClient/server sorting, custom sort functions
HIGHColumn Filteringrules/filt-column-filtering.mdPer-column filters, custom filter functions
HIGHGlobal Filteringrules/filt-global-filtering.mdTable-wide search, global filter function
HIGHPaginationrules/pag-pagination.mdClient/server pagination, page state
MEDIUMRow Selectionrules/sel-row-selection.mdCheckbox/radio selection, selection state
MEDIUMColumn Visibilityrules/vis-column-visibility.mdShow/hide columns dynamically
MEDIUMColumn Sizingrules/size-column-sizing.mdWidths, resizing, performance
LOWExpandingrules/exp-expanding.mdSub-rows, detail panels, hierarchical data

Critical Rules

Always Do

  • Stable data reference — use useState, useMemo, or define outside component to prevent infinite re-renders
  • Use createColumnHelper<TData>() — for maximum type safety in column definitions
  • Import only needed row models — don't import getSortedRowModel if you don't sort client-side
  • Use flexRender — for rendering header/cell/footer templates from column defs
  • Use getVisibleCells() — not getAllCells(), to respect column visibility
  • Use getRowModel() — the final row model that applies all features (filtering, sorting, pagination)
  • Control state with state + on*Change — for sorting, filtering, pagination, selection, etc.

Never Do

  • Define data inlineuseReactTable({ data: fetchData() }) causes infinite re-renders
  • Define columns inside render — columns array must be stable (define outside component or useMemo)
  • Use getAllCells() for rendering — ignores column visibility; use getVisibleCells()
  • Mix initialState and state for the same featurestate overrides initialState
  • Use client-side row models with manual* options — if manualSorting: true, don't import getSortedRowModel
  • Forget getRowId — without it, row IDs default to index, breaking selection state across re-fetches

Key Patterns

// Controlled sorting state
const [sorting, setSorting] = useState<SortingState>([])
const table = useReactTable({
  data, columns,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(),
  state: { sorting },
  onSortingChange: setSorting,
})
// Header click handler
<th onClick={header.column.getToggleSortingHandler()}>
  {flexRender(header.column.columnDef.header, header.getContext())}
  {{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? ''}
</th>

// Server-side pagination
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 })
const table = useReactTable({
  data, columns,
  getCoreRowModel: getCoreRowModel(),
  manualPagination: true,
  rowCount: serverData.totalRows,
  state: { pagination },
  onPaginationChange: setPagination,
})

// Row selection with checkbox column
columnHelper.display({
  id: 'select',
  header: ({ table }) => (
    <input type="checkbox" checked={table.getIsAllRowsSelected()}
      onChange={table.getToggleAllRowsSelectedHandler()} />
  ),
  cell: ({ row }) => (
    <input type="checkbox" checked={row.getIsSelected()}
      disabled={!row.getCanSelect()}
      onChange={row.getToggleSelectedHandler()} />
  ),
})

// Column filtering
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const table = useReactTable({
  data, columns,
  getCoreRowModel: getCoreRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  state: { columnFilters },
  onColumnFiltersChange: setColumnFilters,
})
// Filter input
<input value={column.getFilterValue() ?? ''} onChange={e => column.setFilterValue(e.target.value)} />

// Stable row IDs for selection across re-fetches
const table = useReactTable({
  data, columns,
  getRowId: (row) => row.uuid,
  getCoreRowModel: getCoreRowModel(),
})

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

docker

No summary provided by upstream source.

Repository SourceNeeds Review
General

commit-work

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

clean-code

No summary provided by upstream source.

Repository SourceNeeds Review
General

vercel-composition-patterns

No summary provided by upstream source.

Repository SourceNeeds Review