TanStack Table: Data Grids Headless
Aula 3 de 5
Construindo Tabelas com TanStack Table
O TanStack Table (antigo React Table) é uma biblioteca headless para criar data grids. Ela controla estado lógico (sorting, filtering, pagination, selection) enquanto você controla a renderização.
Configuração Inicial
npm install @tanstack/react-table
Tabela Básica
import { useReactTable, getCoreRowModel, flexRender, createColumnHelper } from '@tanstack/react-table'
type User = { id: string; name: string; email: string; role: string }
const data: User[] = [
{ id: '1', name: 'Ana Silva', email: '[email protected]', role: 'Admin' },
{ id: '2', name: 'Carlos Lima', email: '[email protected]', role: 'Editor' },
]
const columnHelper = createColumnHelper<User>()
const columns = [
columnHelper.accessor('name', { header: 'Nome', cell: info => info.getValue() }),
columnHelper.accessor('email', { header: 'E-mail' }),
columnHelper.accessor('role', { header: 'Função' }),
]
function UserTable() {
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>
)
}
Sorting e Filtering
import { getSortedRowModel, getFilteredRowModel } from '@tanstack/react-table'
function Table() {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const table = useReactTable({
data,
columns,
state: { sorting, columnFilters },
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
enableColumnFilters: true,
})
return (
<div>
<input
placeholder="Filtrar nome..."
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={e => table.getColumn('name')?.setFilterValue(e.target.value)}
/>
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id} onClick={header.column.getToggleSortingHandler()}>
{flexRender(header.column.columnDef.header, header.getContext())}
{{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? null}
</th>
))}
</tr>
))}
</thead>
<tbody>{/* ... */}</tbody>
</table>
</div>
)
}
Paginação
import { getPaginationRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageSize: 10 } },
})
// Controles
<div>
<button onClick={() => table.firstPage()} disabled={!table.getCanPreviousPage()}>
{'<<'}
</button>
<button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
{'<'}
</button>
<span>
Página {table.getState().pagination.pageIndex + 1} de {table.getPageCount()}
</span>
<button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
{'>'}
</button>
<button onClick={() => table.lastPage()} disabled={!table.getCanNextPage()}>
{'>>'}
</button>
</div>
Row Selection e Column Visibility
import { getCoreRowModel } from '@tanstack/react-table'
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
enableRowSelection: true,
enableColumnVisibility: true,
})
// Checkbox no header da seleção
columnHelper.display({
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
),
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
/>
),
})
// Toggle visibilidade
<table.ColumnVisibility>
{table.getAllColumns().map(col => (
<label key={col.id}>
<input
type="checkbox"
checked={col.getIsVisible()}
onChange={col.getToggleVisibilityHandler()}
/>
{col.columnDef.header as string}
</label>
))}
</table.ColumnVisibility>
Virtualização com @tanstack/react-virtual
import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualizedTable() {
const parentRef = useRef<HTMLDivElement>(null)
const table = useReactTable({
data: largeDataset,
columns,
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
overscan: 10,
})
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualRow => {
const row = rows[virtualRow.index]
return (
<div
key={row.id}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px)`,
height: `${virtualRow.size}px`,
}}
>
{row.getVisibleCells().map(cell => (
<span key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</span>
))}
</div>
)
})}
</div>
</div>
)
}
Células Editáveis
function EditableCell({ getValue, row, column, table }: CellContext<User, string>) {
const initialValue = getValue()
const [value, setValue] = useState(initialValue)
const onBlur = () => {
table.options.meta?.updateData(row.index, column.id, value)
}
useEffect(() => { setValue(initialValue) }, [initialValue])
return <input value={value} onChange={e => setValue(e.target.value)} onBlur={onBlur} />
}
Lab: Data Grid Completo
npm create vite@latest table-demo -- --template react-ts
cd table-demo
npm install @tanstack/react-table @tanstack/react-virtual
// Construa:
// 1. Tabela com 1000+ linhas simuladas
// 2. Sorting multi-coluna
// 3. Filtro global + filtro por coluna
// 4. Paginação com pageSize configurável
// 5. Seleção de linhas com ações em lote
// 6. Virtualização para performance
// 7. Uma coluna editável inline
TanStack Table é a camada lógica perfeita para data grids — você controla o visual, a biblioteca controla o estado complexo de tabelas.