Хук useQuery в React Query возвращает опциональный объект data, он может быть или не быть, зависит от результата загрузки. Если использовать хук useSuspenseQuery, который работает с Suspense, то data будет всегда. Пример использования:
function Example() {
const { data } = useSuspenseQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
});
return (
<ul>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
function Parent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Example />
</ErrorBoundary>
</Suspense>
);
}
В примере Suspense ответственен за отображение индикатора загрузки. Если запрос закончится неуспешно, то ErrorBoundary отобразит ошибку.
В этом подходе есть ограничения. Например, если изменится queryKey, то Suspense заново отобразит индикатор загрузки. Если надо чтобы компонент Example продолжал отображаться во время повторной загрузки, то придется дополнительно использовать useTransition или useDeferredValue.
Пример с useTransition:
import { useTransition, useState } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
function Example() {
const [page, setPage] = useState(1);
const [isPending, startTransition] = useTransition();
const { data } = useSuspenseQuery({
queryKey: ["todos", page],
queryFn: () => fetchTodos({ page }),
});
function handleNextPage() {
startTransition(() => {
setPage((prev) => prev + 1);
});
}
return (
<div>
{isPending && <p>Fetching...</p>}
<ul>{/* ...render todos... */}</ul>
<button onClick={handleNextPage}>Next</button>
</div>
);
}
https://www.teemutaskula.com/blog/exploring-query-suspense