Carregando recursos ...
paginacao com supabase e reactsupabasereacttanstack queryscroll infinitosupabase range

Paginação com Supabase e React: guia com range()

SCStormcore
5 min de leitura
Paginação com Supabase e React: guia com range()

Paginação com Supabase e React: como fazer do jeito certo

A paginação com Supabase e React se resolve com o método range(from, to) do supabase-js, que retorna apenas um intervalo de linhas da tabela. No React, você controla o número da página em um estado (ou usa o useInfiniteQuery do TanStack Query) e calcula o intervalo a cada mudança. Assim o app carrega só o necessário, fica mais rápido e não estoura o limite padrão de linhas por requisição.

Por que paginar os dados do Supabase

Buscar uma tabela inteira com select('*') funciona em desenvolvimento, mas vira problema em produção. O Supabase usa o PostgREST por trás, que aplica um limite máximo de linhas por resposta (o padrão em projetos Supabase é 1000). Se sua tabela passar disso, os dados simplesmente vêm cortados.

Além do limite, paginar traz ganhos práticos:

  • Menos dados trafegando na rede, o que melhora o carregamento em celulares.
  • Menos renderização no React, com listas leves e interface fluida.
  • Menor consumo de banda e de leituras no banco.
  • Melhor experiência para o usuário, que vê conteúdo rápido.

Como usar o range() do supabase-js

O range(from, to) usa índices que começam em zero e incluem o último valor. Ou seja, range(0, 9) retorna 10 linhas. Um helper simples calcula o intervalo a partir da página e do tamanho:

code
const PAGE_SIZE = 10

async function buscarProdutos(pagina) {
  const from = pagina * PAGE_SIZE
  const to = from + PAGE_SIZE - 1

  const { data, error, count } = await supabase
    .from('produtos')
    .select('*', { count: 'exact' })
    .order('created_at', { ascending: false })
    .range(from, to)

  if (error) throw error
  return { data, count }
}

Dois detalhes importam aqui. Primeiro, sempre use order(): sem uma ordenação estável, a mesma linha pode aparecer em duas páginas ou sumir entre elas. Segundo, a opção count: 'exact' devolve o total de registros, útil para calcular o número de páginas, mas ela tem custo em tabelas muito grandes. Nesses casos, considere count: 'estimated'.

Paginação numerada no React

Para uma lista com botões "anterior" e "próxima", guarde a página em um estado e use o TanStack Query para cachear cada página:

code
const [pagina, setPagina] = useState(0)

const { data, isPending } = useQuery({
  queryKey: ['produtos', pagina],
  queryFn: () => buscarProdutos(pagina),
  placeholderData: keepPreviousData,
})

const totalPaginas = Math.ceil((data?.count ?? 0) / PAGE_SIZE)

O keepPreviousData mantém a página anterior na tela enquanto a nova carrega, evitando que a lista pisque. A queryKey com o número da página faz cada página ser cacheada separadamente.

Scroll infinito com useInfiniteQuery

Em feeds e catálogos, o scroll infinito costuma funcionar melhor que botões. A documentação do TanStack Query v5 pede que o useInfiniteQuery receba initialPageParam e getNextPageParam:

code
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery({
    queryKey: ['produtos-infinito'],
    queryFn: ({ pageParam }) => buscarProdutos(pageParam),
    initialPageParam: 0,
    getNextPageParam: (ultimaPagina, todasPaginas) =>
      ultimaPagina.data.length === PAGE_SIZE ? todasPaginas.length : undefined,
  })

Quando getNextPageParam retorna undefined, o hasNextPage vira falso e o carregamento para. Para disparar fetchNextPage, use um IntersectionObserver em um elemento no fim da lista, ou o botão "carregar mais".

Offset ou cursor: qual escolher

O range() usa offset, que é simples e ótimo para a maioria dos sites. O problema aparece em tabelas enormes: quanto maior o offset, mais o Postgres precisa ler e descartar linhas, e a consulta desacelera.

A alternativa é a paginação por cursor (keyset): você pede "os 10 registros com created_at menor que o último que eu vi", usando .lt('created_at', ultimoValor).order('created_at', { ascending: false }).limit(10). Ela mantém o desempenho estável e evita duplicatas quando novos itens entram durante a navegação. Crie um índice na coluna usada como cursor.

Regra prática: até algumas dezenas de milhares de linhas, range() resolve. Acima disso, ou em feeds que mudam o tempo todo, prefira cursor.

Perguntas Frequentes

O range() do Supabase é inclusivo? Sim. Os dois valores contam e o índice começa em zero. range(0, 9) retorna as 10 primeiras linhas.

Qual o limite de linhas por consulta no Supabase? O padrão em projetos Supabase é de 1000 linhas por requisição, configurável nas configurações da API. Por isso paginar é necessário em tabelas grandes.

Como saber o total de páginas? Use select('*', { count: 'exact' }) e divida o count pelo tamanho da página com Math.ceil.

Posso paginar sem TanStack Query? Pode. Um useState com useEffect funciona, mas você perde cache, estados de carregamento e a revalidação automática que a biblioteca entrega.

Paginação por offset ou cursor, qual é mais rápida? O cursor mantém desempenho constante em tabelas grandes, já o offset desacelera conforme a página avança.

Conclusão

Para implementar a paginação com Supabase e React, comece com range() e order(), adicione o TanStack Query para cache e evolua para cursor se a tabela crescer muito. Na Stormcore, criamos sites e sistemas em React e Supabase pensando em desempenho desde o primeiro dia, e uma boa paginação é parte disso.

Continue lendo

Matérias relacionadas

Pronto para tirar sua ideia do papel?

Fale agora com nosso time de desenvolvimento. Sites, apps, automações, o que você precisar.

Chamar no WhatsApp

Resposta em até 2 horas