Capítulo 66 de 108
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).
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.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.// 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
// 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>
)
}
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,
}),
])
display (sem accessor) para checkbox de seleção all-page/per-row.| Passo do guia | O que adiciona |
|---|---|
| Basic Table | columns.tsx + data-table.tsx + render em page.tsx |
| Cell Formatting | Intl.NumberFormat no cell de uma coluna accessor |
| Row Actions | coluna display com DropdownMenu |
| Pagination | rowPaginationFeature + createPaginatedRowModel() (já habilitado por padrão em páginas de 10) |
| Sorting | rowSortingFeature + estado sorting + column.toggleSorting() no header |
| Filtering | columnFilteringFeature + Input ligado a table.getColumn(id)?.setFilterValue() |
| Visibility | columnVisibilityFeature + DropdownMenuCheckboxItem por coluna |
| Row Selection | rowSelectionFeature + coluna select com Checkbox |
filterFns/sortFns usados pelas colunas: a coluna referencia a função por nome (includesString, text), mas ela só existe se declarada em tableFeatures().<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).npx shadcn@latest add data-table de fato funcional sozinho — é add table + npm install @tanstack/react-table + seguir o guia manualmente.tableFeatures(); isso é uma mudança de v8 e precisa ser compartilhado (data-table-features.ts) entre columns, table component e página.table.FlexRender (v9, sem import) substitui flexRender() (v8, precisa de import) para renderizar header/cell.React.useState + on*Change callbacks passados a useTable.