import { Callout } from 'nextra-theme-docs'
Por favor, actualice a la última versión (≥ 0.3.0) para utilizar esta API. La anterior APIuseSWRPages
ha quedado obsoleta.
SWR proporciona una API dedicada useSWRInfinite
para admitir patrones de UI comunes como la paginación y la carga infinita.
En primer lugar, es posible que NO necesitemos useSWRInfinite
, sino que podemos utilizar simplemente useSWR
si estamos construyendo algo como esto:
import { Pagination } from '@components/diagrams/pagination'
...que es un típico UI de paginación. Veamos cómo se puede implementar fácilmente con
useSWR
:
function App () {
const [pageIndex, setPageIndex] = useState(0);
// La URL de la API incluye el índice de la página, que es un React state.
const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);
// ... manejar los estados de carga y error
return <div>
{data.map(item => <div key={item.id}>{item.name}</div>)}
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
}
Además, podemos crear una abstracción para este "page component":
function Page ({ index }) {
const { data } = useSWR(`/api/data?page=${index}`, fetcher);
// ... manejar los estados de carga y error
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)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
}
Gracias a la caché de SWR, tenemos la ventaja de precargar la siguiente página. La página siguiente se presenta dentro de un hidden div, por lo que SWR activará la obtención de datos de la página siguiente. Cuando el usuario navega a la siguiente página, los datos ya están allí:
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>
}
Con sólo 1 línea de código, conseguimos una UX mucho mejor. El hook useSWR
es tan potente,
que la mayoría de los escenarios están cubiertos por él.
A veces queremos construir una UI de carga infinita, con un botón "Load More" que añada datos a la lista (o que lo haga automáticamente al desplazarse):
import { Infinite } from '@components/diagrams/infinite'
Para implementar esto, necesitamos hacer número de peticiones dinámicas en esta página. Los React Hooks tienen un par de reglas, por lo que NO PODEMOS hacer algo así:
function App () {
const [cnt, setCnt] = useState(1)
const list = []
for (let i = 0; i < cnt; i++) {
// 🚨 Esto es un error. Comúnmente, no se pueden usar hooks dentro de un bucle.
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)}>Load More</button>
</div>
}
En su lugar, podemos utilizar la abstracción <Page />
que hemos creado para conseguirlo:
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)}>Load More</button>
</div>
}
Sin embargo, en algunos casos de uso avanzado, la solución anterior no funciona.
Por ejemplo, seguimos implementando la misma UI "Load More", pero también necesitamos mostrar un número
sobre cuántos item hay en total. No podemos utilizar la solución <Page />
porque
la UI de nivel superior (<App />
) necesita los datos 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)}>Load More</button>
</div>
}
Además, si la API de paginación es cursor based, esa solución tampoco funciona. Porque cada página necesita los datos de la página anterior, no están aisladas.
Así es como este nuevo hook useSWRInfinite
puede ayudar.
useSWRInfinite
nos da la posibilidad de lanzar un número de peticiones con un solo Hook. Así es como se ve:
import useSWRInfinite from 'swr/infinite'
// ...
const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite(
getKey, fetcher?, options?
)
Al igual que useSWR
, este nuevo hook acepta una función que devuelve la key de la solicitud, un fetcher función y options.
Devuelve todos los valores que devuelve useSWR
, incluyendo 2 valores extra: page size y page size setter, como un React state.
En la carga infinita, una page es una petición, y nuestro objetivo es obtener varias páginas y renderizarlas.
If you are using SWR 0.x versions, `useSWRInfinite` needs to be imported from `swr`:`import { useSWRInfinite } from 'swr'`
getKey
: una función que acepta el índice y los datos de la página anterior, devuelve la key de una páginafetcher
: igual que la función fetcher deuseSWR
options
: acepta todas las opciones que soportauseSWR
, con 3 opciones adicionales:initialSize = 1
: número de páginas que deben cargarse inicialmenterevalidateAll = false
: intentar siempre revalidar todas las páginasrevalidateFirstPage = true
: always try to revalidate the first pagepersistSize = false
: no restablecer el page size a 1 (oinitialSize
si está establecido) cuando la key de la primera página cambia
data
: una array de valores de respuesta fetch de cada páginaerror
: El mismo valor devuelto deerror
queuseSWR
isValidating
: El mismo valor devuelto deisValidating
queuseSWR
mutate
: same asuseSWR
's bound mutate function but manipulates the data arraysize
: el número de páginas que se obtendrán y devolveránsetSize
: establecer el número de páginas que deben ser recuperadas
Para las APIs normales basadas en índices:
GET /users?page=0&limit=10
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
]
// Una función para obtener la key de SWR de cada página,
// su valor de retorno será aceptado por `fetcher`.
// Si se devuelve `null`, la petición de esa página no se iniciará.
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null // reached the end
return `/users?page=${pageIndex}&limit=10` // SWR key
}
function App () {
const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
if (!data) return 'loading'
// Ahora podemos calcular el número de todos los usuarios
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` es un array con la respuesta de la API de cada página.
return users.map(user => <div key={user.id}>{user.name}</div>)
})}
<button onClick={() => setSize(size + 1)}>Load More</button>
</div>
}
La función getKey
es la mayor diferencia entre useSWRInfinite
y useSWR
.
Acepta el índice de la página actual, así como los datos de la página anterior.
Así que tanto la API de paginación basada en el índice como la basada en el cursor pueden ser soportadas de forma adecuada.
Además, la data
ya no son sólo es una respuesta de la API. Es una array de múltiples respuestas de la API:
// `data` tendrá el siguiente aspecto
[
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
],
[
{ name: 'John', ... },
{ name: 'Paul', ... },
{ name: 'George', ... },
...
],
...
]
Digamos que la API ahora requiere un cursor y devuelve el siguiente cursor junto con los datos:
GET /users?cursor=123&limit=10
{
data: [
{ name: 'Alice' },
{ name: 'Bob' },
{ name: 'Cathy' },
...
],
nextCursor: 456
}
Podemos cambiar nuestra función getKey
por:
const getKey = (pageIndex, previousPageData) => {
// reached the end
if (previousPageData && !previousPageData.data) return null
// la primera página, no tenemos `previousPageData`.
if (pageIndex === 0) return `/users?limit=10`
// añadir el cursor al punto final de la API
return `/users?cursor=${previousPageData.nextCursor}&limit=10`
}
Aquí hay un ejemplo que muestra cómo se pueden implementar las siguientes características con useSWRInfinite
:
- estados de carga
- mostrar una UI especial si está vacía
- desactivar el botón "Load More" si reached the end
- fuente de datos modificable
- actualizar toda la lista