Aprenda / Frontend — Tailwind & Next.js

IntermediárioAula 19 de 2018 min0% da trilha

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.

Paginação server-side
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.

use-debounced-value.ts
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.

Uso simplificado do TanStack Table
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.

Quiz rápido

Qual é a principal desvantagem de paginação client-side pra uma tabela com um volume grande de dados?
Por que aplicar debounce num campo de busca antes de refazer o fetch?