Das Rendern von paginierten Daten ist ein sehr gängiges UI-Muster und in TanStack Query "funktioniert es einfach", indem die Seiteninformationen in den Abfrageschlüssel aufgenommen werden.
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects,
})
const result = useQuery({
queryKey: ['projects', page],
queryFn: fetchProjects,
})
Wenn Sie jedoch dieses einfache Beispiel ausführen, bemerken Sie möglicherweise etwas Seltsames.
Die Benutzeroberfläche springt zwischen den Zuständen success und pending hin und her, da jede neue Seite als völlig neue Abfrage behandelt wird.
Diese Erfahrung ist nicht optimal und leider ist es so, wie viele Tools heute bestehen. Aber nicht TanStack Query! Wie Sie sich vielleicht gedacht haben, verfügt TanStack Query über ein fantastisches Feature namens placeholderData, mit dem wir dies umgehen können.
Betrachten Sie das folgende Beispiel, bei dem wir idealerweise einen Seitenindex (oder Cursor) für eine Abfrage inkrementieren würden. Wenn wir useQuery verwenden würden, **würde es technisch immer noch einwandfrei funktionieren**, aber die Benutzeroberfläche würde zwischen den Zuständen success und pending springen, da für jede Seite oder jeden Cursor verschiedene Abfragen erstellt und zerstört werden. Durch das Setzen von placeholderData auf (previousData) => previousData oder die von TanStack Query exportierte Funktion keepPreviousData erhalten wir einige neue Dinge:
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import React from 'react'
function Todos() {
const [page, setPage] = React.useState(0)
const fetchProjects = (page = 0) =>
fetch('/api/projects?page=' + page).then((res) => res.json())
const { isPending, isError, error, data, isFetching, isPlaceholderData } =
useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
placeholderData: keepPreviousData,
})
return (
<div>
{isPending ? (
<div>Loading...</div>
) : isError ? (
<div>Error: {error.message}</div>
) : (
<div>
{data.projects.map((project) => (
<p key={project.id}>{project.name}</p>
))}
</div>
)}
<span>Current Page: {page + 1}</span>
<button
onClick={() => setPage((old) => Math.max(old - 1, 0))}
disabled={page === 0}
>
Previous Page
</button>
<button
onClick={() => {
if (!isPlaceholderData && data.hasMore) {
setPage((old) => old + 1)
}
}}
// Disable the Next Page button until we know a next page is available
disabled={isPlaceholderData || !data?.hasMore}
>
Next Page
</button>
{isFetching ? <span> Loading...</span> : null}
</div>
)
}
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import React from 'react'
function Todos() {
const [page, setPage] = React.useState(0)
const fetchProjects = (page = 0) =>
fetch('/api/projects?page=' + page).then((res) => res.json())
const { isPending, isError, error, data, isFetching, isPlaceholderData } =
useQuery({
queryKey: ['projects', page],
queryFn: () => fetchProjects(page),
placeholderData: keepPreviousData,
})
return (
<div>
{isPending ? (
<div>Loading...</div>
) : isError ? (
<div>Error: {error.message}</div>
) : (
<div>
{data.projects.map((project) => (
<p key={project.id}>{project.name}</p>
))}
</div>
)}
<span>Current Page: {page + 1}</span>
<button
onClick={() => setPage((old) => Math.max(old - 1, 0))}
disabled={page === 0}
>
Previous Page
</button>
<button
onClick={() => {
if (!isPlaceholderData && data.hasMore) {
setPage((old) => old + 1)
}
}}
// Disable the Next Page button until we know a next page is available
disabled={isPlaceholderData || !data?.hasMore}
>
Next Page
</button>
{isFetching ? <span> Loading...</span> : null}
</div>
)
}
Obwohl nicht so häufig, funktioniert die Option placeholderData auch einwandfrei mit dem Hook useInfiniteQuery, sodass Sie Ihren Benutzern nahtlos weiterhin gecachte Daten anzeigen lassen können, während sich die Infinite Query-Schlüssel im Laufe der Zeit ändern.