Skip to content
Documentação
Paginação

Paginação

Por favor atualize para a versão mais recente (≥ 0.3.0) para usar esta API. A API anterior useSWRPages está descontinuada.

SWR provê uma API useSWRInfinite dedicada para lidar com padrões de interface comuns como paginação e carregamento infinito.

Quando Usar useSWR

Paginação

Antes de tudo, nós podemos NÃO precisar de useSWRInfinite mas podemos usar apenas useSWR se estamos construindo algo como isso:

...que é uma interface típica de paginação. Veja como pode ser facilmente implementado com useSWR:

function App () {
  const [pageIndex, setPageIndex] = useState(0);
 
  // A URL da API inclui o índice da página, que é um estado do React.
  const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);
 
  // ... lidando com estados de loading e erro
 
  return <div>
    {data.map(item => <div key={item.id}>{item.name}</div>)}
    <button onClick={() => setPageIndex(pageIndex - 1)}>Anterior</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Próximo</button>
  </div>
}

Além disso, podemos criar uma abstração para este "componente de página":

function Page ({ index }) {
  const { data } = useSWR(`/api/data?page=${index}`, fetcher);
 
  // ... lidando com estados de loading e erro
 
  return data.map(item => <div key={item.id}>{item.name}</div>)
}
 
function App () {
  const [pageIndex, setPageIndex] = useState(0);
 
  return <div>
    <Page index={pageIndex}/>
    <button onClick={() => setPageIndex(pageIndex - 1)}>Anterior</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Próximo</button>
  </div>
}

Por causa do cache do SWR, temos o benefício de pré-carregar a próxima página. Renderizamos a próxima página dentro um div oculto, SWR acionará a busca de dados da próxima página. Quando o usuário navega para a próxima página, os dados já estão lá:

function App () {
  const [pageIndex, setPageIndex] = useState(0);
 
  return <div>
    <Page index={pageIndex}/>
    <div style={{ display: 'none' }}><Page index={pageIndex + 1}/></div>
    <button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
    <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
  </div>
}

Com apenas 1 linha de código, obtemos uma UX muito melhor. O hook useSWR é tão poderoso que a maioria dos cenários são cobertos por ele.

Carregamento Infinito

Às vezes, queremos criar uma UI de carregamento infinito, com um botão "Carregar mais" que anexa dados para a lista (ou feito automaticamente quando você rola):

Para implementar isso, precisamos fazer um número dinâmico de solicitações nesta página. Os React Hooks tem algumas regras (opens in a new tab), então nós NÃO PODEMOS fazer algo assim:

function App () {
  const [cnt, setCnt] = useState(1)
 
  const list = []
  for (let i = 0; i < cnt; i++) {
    // 🚨 Isso é errado! Comumente, você não pode usar hooks dentro de um loop.
    const { data } = useSWR(`/api/data?page=${i}`)
    list.push(data)
  }
 
  return <div>
    {list.map((data, i) =>
      <div key={i}>{
        data.map(item => <div key={item.id}>{item.name}</div>)
      }</div>)}
    <button onClick={() => setCnt(cnt + 1)}>Carregar mais</button>
  </div>
}

Ao invés disso, podemos usar a abstração <Page /> que criamos para conseguir isso:

function App () {
  const [cnt, setCnt] = useState(1)
 
  const pages = []
  for (let i = 0; i < cnt; i++) {
    pages.push(<Page index={i} key={i} />)
  }
 
  return <div>
    {pages}
    <button onClick={() => setCnt(cnt + 1)}>Carregar mais</button>
  </div>
}

Casos Avançados

Entretanto, em alguns casos avançados, a solução acima não funciona.

Por exemplo, nós ainda estamos implementando o mesmo "Carregar mais", mas também precisamos mostrar um número sobre quantos itens existem no total. Nós não podemos usar o <Page /> novamente porque a top level UI (<App />) precisa dos dados dentro de cada página:

function App () {
  const [cnt, setCnt] = useState(1)
 
  const pages = []
  for (let i = 0; i < cnt; i++) {
    pages.push(<Page index={i} key={i} />)
  }
 
  return <div>
    <p>??? items</p>
    {pages}
    <button onClick={() => setCnt(cnt + 1)}>Carregar mais</button>
  </div>
}

Também, se a API de paginação for baseada em cursor, essa solução não funciona também. Porque cada página precisa dos dados da página interior, elas não são isoladas.

É assim que esse novo hook useSWRInfinite pode ajudar.

useSWRInfinite

useSWRInfinite nos dá a habilidade de disparar uma quantidade de solicitações com um Hook. Isso é como se fosse:

import useSWRInfinite from 'swr/infinite'
 
// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)

Similar ao useSWR, esse novo Hook aceita uma função que retorna a chave de pedido, uma função de busca, e opções. Ele retorna todos os valores que useSWR retorna, incluindo 2 valores extras: o tamanho da página e um setter do tamanho da página, como um state do React.

No carregamento infinito, uma página é uma requisição, e nosso objetivo é buscar múltiplas páginas e renderizá-las.

⚠️

Se você está usando versões SWR 0.x, useSWRInfinite precisa ser importado de swr:
import { useSWRInfinite } from 'swr'

API

Parâmetros

  • getKey: uma função que aceita o índice e os dados da página anterior, retorna a chave de uma página
  • fetcher: mesma função que a função fetcher do useSWR.
  • options: aceita todas as opções que useSWR suporta, com 4 opções adicionais:
    • initialSize=1: número de páginas devem ser carregadas inicialmente
    • revalidateAll=false: sempre tentar revalidar todas as páginas
    • revalidateFirstPage=true: sempre tentar revalidar a primeira página
    • persistSize=false: não reseta o tamanho da página para 1 (ou initialSize se setado) quando a chave da primeira página muda
    • parallel = false: carrega múltiplas páginas em paralelo
💡

Note que a opção initialSize não é permitida a mudar no ciclo de vida.

Valores de Retorno

  • data: um array de respostas de fetch para cada página
  • error: mesma que o objeto error do useSWR.
  • isLoading: mesmo que o isLoading do useSWR.
  • isValidating: mesmo que o isValidating do useSWR.
  • mutate: igual à função de mutação vinculada do useSWR, mas manipula o array de dados
  • size: o número de páginas que vão ser carregadas e retornadas
  • setSize: define o número de páginas que precisam ser carregadas

Exemplo 1: API de Paginação Baseada em Índice

Para APIs baseadas em índice:

GET /users?page=0&limit=10
[
  { name: 'Alice', ... },
  { name: 'Bob', ... },
  { name: 'Cathy', ... },
  ...
]
// A função para obter a chave SWR de cada página,
// o valor retornado será aceito pela função `fetcher`.
// Se o valor `null` é retornado, a solicitação da página não iniciará.
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // atingiu o fim
  return `/users?page=${pageIndex}&limit=10`                    // chave SWR
}
 
function App () {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
  if (!data) return 'loading'
 
  // Agora podemos calcular o número total de usuários
  let totalUsers = 0
  for (let i = 0; i < data.length; i++) {
    totalUsers += data[i].length
  }
 
  return <div>
    <p>{totalUsers} users listed</p>
    {data.map((users, index) => {
      // `data` é um array de respostas da API de cada página.
      return users.map(user => <div key={user.id}>{user.name}</div>)
    })}
    <button onClick={() => setSize(size + 1)}>Carregar mais</button>
  </div>
}

A função getKey é a diferença maior entre useSWRInfinite e useSWR. Ela aceita o índice da página atual, bem como os dados da página anterior. Então ambas (paginação baseada em índice e páginação baseada em cursor) podem ser suportadas.

Também, data não é mais apenas uma resposta da API. É um array de respostas da API:

// `data` se parecerá com isso
[
  [
    { name: 'Alice', ... },
    { name: 'Bob', ... },
    { name: 'Cathy', ... },
    ...
  ],
  [
    { name: 'John', ... },
    { name: 'Paul', ... },
    { name: 'George', ... },
    ...
  ],
  ...
]

Exemplo 2: API de Paginação Baseada em Cursor ou Offset

Vamos dizer que a API agora requer um cursor e retorna o próximo cursor junto com os dados:

GET /users?cursor=123&limit=10
{
  data: [
    { name: 'Alice' },
    { name: 'Bob' },
    { name: 'Cathy' },
    ...
  ],
  nextCursor: 456
}

Nós podemos mudar nossa função getKey para:

const getKey = (pageIndex, previousPageData) => {
  // alcançou o fim
  if (previousPageData && !previousPageData.data) return null
 
  // primeira página, nós não temos `previousPageData`
  if (pageIndex === 0) return `/users?limit=10`
 
  // adiciona o cursor para o endpoint da API
  return `/users?cursor=${previousPageData.nextCursor}&limit=10`
}

Modo de Carregamento Paralelo

Por favor atualize para a última versão (≥ 2.1.0) para usar essa API.

O comportamento padrão do useSWRInfinite é de obter dados para cada página em sequênciaa, pois a criação de chaves é baseada nos dados previamente obtidos. No entanto, fazer fetching de dados sequencialmente para um grande número de páginas pode não ser otimizado, particularmente se as páginas não são interdependentes.

Ao especificar a opção parallel como true, você poderá obter páginas independentemente em paralelo, o que pode acelerar significativamente o processo de carregamento.

// parallel = false (default)
// page1 ===> page2 ===> page3 ===> feito
//
// parallel = true
// page1 ==> feito
// page2 =====> feito
// page3 ===> feito
//
// previousPageData é sempre `null`
const getKey = (pageIndex, previousPageData) => {
  return `/users?page=${pageIndex}&limit=10`
}
 
function App () {
  const { data } = useSWRInfinite(getKey, fetcher, { parallel: true })
}
⚠️

O argumento previousPageData da função getKey se torna null quando você habilita a opção parallel.

Revalidate Specific Pages

Please update to the latest version (≥ 2.2.5) to use this API.

The default behavior of the mutation of useSWRInfinite is to revalidate all pages that have been loaded. But you might want to revalidate only the specific pages that have been changed. You can revalidate only specific pages by passing a function to the revalidate option.

The revalidate function is called for each page.

function App() {
  const { data, mutate, size } = useSWRInfinite(
    (index) => [`/api/?page=${index + 1}`, index + 1],
    fetcher
  );
 
  mutate(data, {
    // only revalidate the last page
    revalidate: (pageData, [url, page]) => page === size
  });
}

Mutação Global com useSWRInfinite

useSWRInfinite armazena todos os dados da página com uma chave de cache especial junto com cada dado da página. Você deve usar unstable_serialize em swr/infinite para revalidar os dados com a mutação global.

import { useSWRConfig } from "swr"
import { unstable_serialize } from "swr/infinite"
 
function App() {
    const { mutate } = useSWRConfig()
    mutate(unstable_serialize(getKey))
}
⚠️

Como o nome indica, unstable_serialize não é uma API estável, então podemos mudá-la no futuro.

Funcionalidades Avançadas

Um exemplo mostrando como você pode implementar as seguintes funcionalidades com useSWRInfinite está disponível aqui:

  • Estados de carregamento
  • Mostrar uma interface especial se estiver vazio
  • Desativar o botão "Carregar mais" se alcançou o fim
  • Fonte de dados alterável
  • Atualizar toda a lista