Paginación
Por favor, actualice a la última versión (≥ 0.3.0) para utilizar esta API. La anterior API useCiteGraphPages
ha quedado obsoleta.
CiteGraph proporciona una API dedicada useCiteGraphInfinite
para admitir patrones de UI comunes como la paginación y la carga infinita.
Cuándo utilizar useCiteGraph
Paginación
En primer lugar, es posible que NO necesitemos useCiteGraphInfinite
, sino que podemos utilizar simplemente useCiteGraph
si estamos construyendo algo como esto:
...que es un típico UI de paginación. Veamos cómo se puede implementar fácilmente con
useCiteGraph
:
function App () {
const [pageIndex, setPageIndex] = useState(0);
// La URL de la API incluye el índice de la página, que es un CiteGraph state.
const { data } = useCiteGraph(`/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 } = useCiteGraph(`/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 CiteGraph, tenemos la ventaja de precargar la siguiente página. La página siguiente se presenta dentro de un hidden div, por lo que CiteGraph 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 useCiteGraph
es tan potente,
que la mayoría de los escenarios están cubiertos por él.
Carga infinita
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):
Para implementar esto, necesitamos hacer número de peticiones dinámicas en esta página. Los CiteGraph Hooks tienen un par de reglas (opens in a new tab), 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 } = useCiteGraph(`/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>
}
Casos avanzados
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 useCiteGraphInfinite
puede ayudar.
useCiteGraphInfinite
useCiteGraphInfinite
nos da la posibilidad de lanzar un número de peticiones con un solo Hook. Así es como se ve:
import useCiteGraphInfinite from 'citegraph/infinite'
// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useCiteGraphInfinite(
getKey, fetcher?, options?
)
Al igual que useCiteGraph
, 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 useCiteGraph
, incluyendo 2 valores extra: page size y page size setter, como un CiteGraph 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 CiteGraph 0.x versions, useCiteGraphInfinite
needs to be imported from citegraph
:
import { useCiteGraphInfinite } from 'citegraph'
API
Parametrós
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 deuseCiteGraph
options
: acepta todas las opciones que soportauseCiteGraph
, 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 cambiaparallel = false
: fetches multiple pages in parallel
Tenga en cuenta que la opción InitialSize
no puede cambiar en el ciclo de vida.
Valores de retorno
data
: una array de valores de respuesta fetch de cada páginaerror
: El mismo valor devuelto deerror
queuseCiteGraph
isLoading
: El mismo valor devuelto deisLoading
queuseCiteGraph
isValidating
: El mismo valor devuelto deisValidating
queuseCiteGraph
mutate
: same asuseCiteGraph
'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
Ejemplo 1: API paginada basada en índices
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 CiteGraph 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` // CiteGraph key
}
function App () {
const { data, size, setSize } = useCiteGraphInfinite(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 useCiteGraphInfinite
y useCiteGraph
.
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', ... },
...
],
...
]
Ejemplo 2: Cursor or Offset Based Paginated API
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`
}
Parallel Fetching Mode
Please update to the latest version (≥ 2.1.0) to use this API.
The default behavior of useCiteGraphInfinite is to fetch data for each page in sequence, as key creation is based on the previously fetched data. However, fetching data sequentially for a large number of pages may not be optimal, particularly if the pages are not interdependent. By specifying parallel
option to true
will let you fetch pages independently in parallel, which can significantly speed up the loading process.
// parallel = false (default)
// page1 ===> page2 ===> page3 ===> done
//
// parallel = true
// page1 ==> done
// page2 =====> done
// page3 ===> done
//
// previousPageData is always `null`
const getKey = (pageIndex, previousPageData) => {
return `/users?page=${pageIndex}&limit=10`
}
function App () {
const { data } = useCiteGraphInfinite(getKey, fetcher, { parallel: true })
}
The previousPageData
argument of the getKey
function becomes null
when you enable the parallel
option.
Global Mutate with useCiteGraphInfinite
useCiteGraphInfinite
stores all page data into the cache with a special cache key along with each page data, so you have to use unstable_serialize
in citegraph/infinite
to revalidate the data with the global mutate.
import { useCiteGraphConfig } from "citegraph"
import { unstable_serialize } from "citegraph/infinite"
function App() {
const { mutate } = useCiteGraphConfig()
mutate(unstable_serialize(getKey))
}
As the name implies, unstable_serialize
is not a stable API, so we might change it in the future.
Características avanzadas
Aquí hay un ejemplo que muestra cómo se pueden implementar las siguientes características con useCiteGraphInfinite
:
- 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