Capítulo 66 de 108

Data Table

Core Idea

shadcn/ui não oferece um componente <DataTable> pronto: em vez disso, é um guia para construir sua própria data table combinando o componente Table (ch065) com TanStack Table v9, que é feature-based (opt-in por feature, tree-shakeable).

Key Concepts

  • tableFeatures({...}): declara explicitamente quais features a tabela usa (sorting, filtering, pagination, row selection, column visibility). Features não registradas são removidas do bundle no build.
  • createColumnHelper<typeof features, TData>(): cria o helper de colunas tipado, amarrado ao objeto de features e ao tipo de dado da linha.
  • columnHelper.accessor(key, def) vs columnHelper.display(def): accessor para colunas ligadas a um campo de dado; display para colunas sem dado próprio (checkbox de seleção, coluna de ações).
  • useTable({ features, data, columns, ...state }): hook principal que instancia a tabela; estado (sorting, filtros, visibilidade, seleção) é controlado externamente via React.useState + callbacks on*Change.
  • table.FlexRender: método do v9 (<table.FlexRender header={header} /> / cell={cell}) para renderizar células/headers sem import extra — substitui o flexRender() do v8, que ainda funciona.
  • Row model core sempre incluso: você nunca registra o row model básico; apenas os opcionais (createFilteredRowModel(), createPaginatedRowModel(), createSortedRowModel()) via features.
  • filterFns / sortFns: funções de filtro/ordenação (ex. filterFn_includesString, sortFn_text, sortFn_alphanumeric) também precisam ser registradas explicitamente no objeto de features.

Code Examples

// data-table-features.ts — objeto compartilhado entre columns.tsx, data-table.tsx e page.tsx
import {
  columnFilteringFeature,
  columnVisibilityFeature,
  createFilteredRowModel,
  createPaginatedRowModel,
  createSortedRowModel,
  filterFn_includesString,
  rowPaginationFeature,
  rowSelectionFeature,
  rowSortingFeature,
  sortFn_alphanumeric,
  sortFn_text,
  tableFeatures,
} from "@tanstack/react-table"

export const features = tableFeatures({
  columnFilteringFeature,
  columnVisibilityFeature,
  rowPaginationFeature,
  rowSelectionFeature,
  rowSortingFeature,
  filteredRowModel: createFilteredRowModel(),
  paginatedRowModel: createPaginatedRowModel(),
  sortedRowModel: createSortedRowModel(),
  filterFns: { includesString: filterFn_includesString },
  sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
})

export type DataTableFeatures = typeof features
  • O que demonstra: setup central de features tree-shakeable, importado por todos os outros arquivos da data table.
// data-table.tsx
export function DataTable<TData extends RowData>({ columns, data }: DataTableProps<TData>) {
  const [sorting, setSorting] = React.useState<SortingState>([])
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
  const [columnVisibility, setColumnVisibility] = React.useState<ColumnVisibilityState>({})
  const [rowSelection, setRowSelection] = React.useState({})

  const table = useTable({
    features,
    data,
    columns,
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    onColumnVisibilityChange: setColumnVisibility,
    onRowSelectionChange: setRowSelection,
    state: { sorting, columnFilters, columnVisibility, rowSelection },
  })

  return (
    <Table>
      <TableHeader>
        {table.getHeaderGroups().map((headerGroup) => (
          <TableRow key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <TableHead key={header.id}>
                {header.isPlaceholder ? null : <table.FlexRender header={header} />}
              </TableHead>
            ))}
          </TableRow>
        ))}
      </TableHeader>
      <TableBody>
        {table.getRowModel().rows.map((row) => (
          <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
            {row.getVisibleCells().map((cell) => (
              <TableCell key={cell.id}><table.FlexRender cell={cell} /></TableCell>
            ))}
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}
  • O que demonstra: o componente DataTable reusável completo, com sort/filter/visibility/selection acoplados via estado controlado.
// columns.tsx — coluna de seleção (checkbox) + coluna de ações (DropdownMenu)
export const columns = columnHelper.columns([
  columnHelper.display({
    id: "select",
    header: ({ table }) => (
      <Checkbox
        checked={table.getIsAllPageRowsSelected()}
        indeterminate={table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()}
        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
      />
    ),
    cell: ({ row }) => (
      <Checkbox checked={row.getIsSelected()} onCheckedChange={(value) => row.toggleSelected(!!value)} />
    ),
    enableSorting: false,
    enableHiding: false,
  }),
])
  • O que demonstra: coluna display (sem accessor) para checkbox de seleção all-page/per-row.

Reference Tables

Passo do guiaO que adiciona
Basic Tablecolumns.tsx + data-table.tsx + render em page.tsx
Cell FormattingIntl.NumberFormat no cell de uma coluna accessor
Row Actionscoluna display com DropdownMenu
PaginationrowPaginationFeature + createPaginatedRowModel() (já habilitado por padrão em páginas de 10)
SortingrowSortingFeature + estado sorting + column.toggleSorting() no header
FilteringcolumnFilteringFeature + Input ligado a table.getColumn(id)?.setFilterValue()
VisibilitycolumnVisibilityFeature + DropdownMenuCheckboxItem por coluna
Row SelectionrowSelectionFeature + coluna select com Checkbox

Anti-patterns

  • Registrar todas as features "por garantia": quebra o propósito do design tree-shakeable do v9; registre só o que a tabela realmente usa.
  • Esquecer de registrar filterFns/sortFns usados pelas colunas: a coluna referencia a função por nome (includesString, text), mas ela só existe se declarada em tableFeatures().
  • Recriar um componente <DataTable> monolítico e genérico demais: o próprio guia recomenda manter columns.tsx específico por tabela e extrair só peças reais reutilizáveis (column header, pagination, view options).

Key Takeaways

  1. Não existe npx shadcn@latest add data-table de fato funcional sozinho — é add table + npm install @tanstack/react-table + seguir o guia manualmente.
  2. TanStack Table v9 é opt-in por feature via tableFeatures(); isso é uma mudança de v8 e precisa ser compartilhado (data-table-features.ts) entre columns, table component e página.
  3. table.FlexRender (v9, sem import) substitui flexRender() (v8, precisa de import) para renderizar header/cell.
  4. Estado de sorting/filtros/visibilidade/seleção é sempre controlado externamente via React.useState + on*Change callbacks passados a useTable.
  5. Componentes reutilizáveis prontos (column header sortável, pagination com contagem, column visibility toggle) existem como referência no demo "Tasks", não como parte do registry base.

Connects To

  • Table (ch065): a camada de marcação/estilo sobre a qual a Data Table é construída.
  • DropdownMenu: usado tanto para ações de linha quanto para o toggle de visibilidade de colunas.
  • Chart (ch067): junto com Data Table, compõe dashboards típicos de app admin.