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áginafetcher
: mesma função que a função fetcher douseSWR
.options
: aceita todas as opções queuseSWR
suporta, com 4 opções adicionais:initialSize=1
: número de páginas devem ser carregadas inicialmenterevalidateAll=false
: sempre tentar revalidar todas as páginasrevalidateFirstPage=true
: sempre tentar revalidar a primeira páginapersistSize=false
: não reseta o tamanho da página para 1 (ouinitialSize
se setado) quando a chave da primeira página mudaparallel = 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áginaerror
: mesma que o objetoerror
douseSWR
.isLoading
: mesmo que oisLoading
douseSWR
.isValidating
: mesmo que oisValidating
douseSWR
.mutate
: igual à função de mutação vinculada douseSWR
, mas manipula o array de dadossize
: o número de páginas que vão ser carregadas e retornadassetSize
: 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