Tabelas de dados: paginação, ordenação e filtro
Buscar tudo com um fetch (lição 8) funciona com 20 linhas. Com 5 mil, a lista inteira em memória vira o problema.
Buscar dado com um fetch direto no Server Component, como na lição de data fetching, funciona muito bem pra uma lista de 20 itens. Quando a lista cresce pra centenas ou milhares de linhas, isso deixa de ser só "como buscar" e vira "como buscar em pedaços" — é aí que entram paginação, ordenação e filtro como parte do próprio fetching, não só da exibição.
Paginação client-side vs. server-side
Client-side: busca a lista inteira de uma vez e corta em páginas no navegador. Simples de implementar, mas ruim quando o total de linhas é grande — o navegador precisa baixar e manter tudo em memória mesmo mostrando só uma página. Server-side: cada página é uma requisição nova (?page=2&pageSize=20), o backend devolve só aquele pedaço. Escala melhor, ao custo de uma requisição por troca de página.
async function getPedidos(page: number, pageSize = 20) {
const res = await fetch(`https://api.exemplo.com/pedidos?page=${page}&pageSize=${pageSize}`, {
cache: "no-store",
});
return res.json() as Promise<{ itens: Pedido[]; total: number }>;
}Filtro com debounce
Se cada tecla digitada num campo de busca disparasse um novo fetch, uma busca de 10 caracteres viraria 10 requisições. Debounce espera o usuário parar de digitar por um tempo (ex.: 300ms) antes de disparar a busca de verdade.
function useDebouncedValue<T>(valor: T, delay: number) {
const [debounced, setDebounced] = useState(valor);
useEffect(() => {
const timeout = setTimeout(() => setDebounced(valor), delay);
return () => clearTimeout(timeout); // cancela se o valor mudar antes do delay passar
}, [valor, delay]);
return debounced;
}
function FiltroDePedidos({ onBuscar }: { onBuscar: (termo: string) => void }) {
const [termo, setTermo] = useState("");
const termoDebounced = useDebouncedValue(termo, 300);
useEffect(() => {
onBuscar(termoDebounced); // só dispara 300ms depois da última tecla
}, [termoDebounced, onBuscar]);
return <input value={termo} onChange={(e) => setTermo(e.target.value)} placeholder="Buscar pedido..." />;
}TanStack Table — quando a tabela cresce em complexidade
TanStack Table é headless: ele não renderiza HTML nenhum pronto, só cuida da lógica — quais linhas mostrar, em que ordem, como paginar — e deixa você desenhar o JSX/Tailwind por cima. Vale a pena quando a tabela ganha colunas configuráveis, ordenação múltipla, ou lógica de linha complexa demais pra reimplementar na mão.
import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender } from "@tanstack/react-table";
const columns = [
{ accessorKey: "cliente", header: "Cliente" },
{ accessorKey: "total", header: "Total" },
];
function TabelaDePedidos({ pedidos }: { pedidos: Pedido[] }) {
const table = useReactTable({
data: pedidos,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), // ordenação por coluna, sem reimplementar na mão
});
return (
<table>
<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>
);
}Exercício
Implemente um hook useDebouncedValue(valor, delay) do zero (sem olhar o exemplo acima) que só atualiza o valor retornado depois que valor parar de mudar por delay milissegundos.