Доки по разработке
This project is maintained by teniryte
В реальных приложениях часто нужно загружать несколько ресурсов одновременно или дожидаться завершения одного запроса перед запуском другого. TanStack Query v5 предлагает специализированные API.
useQueriesimport { useQueries } from '@tanstack/react-query';
const userDashboardQueries = (id: string) => [
{ ...userQuery(id) },
{ ...postsQuery(id), staleTime: 60_000 },
{ ...statsQuery(id), enabled: id !== 'guest' },
];
export function Dashboard({ userId }: { userId: string }) {
const results = useQueries({ queries: userDashboardQueries(userId) });
const [user, posts, stats] = results;
if (user.isPending || posts.isPending) return <Skeleton />;
return <DashboardView user={user.data} posts={posts.data} stats={stats.data} />;
}
QueryOptions.useQueries возвращает массив состояний в том же порядке.queries, иначе каждый ререндер создаёт новые ссылки и заставляет хук перезапускать работу.combine и бросайте исключение, когда results.some(r => r.error).useSuspenseQueries — та же идея, но сразу дружит с React Suspense.queryClient разделяет кеш между всеми хуками, поэтому параллельные useQueries мгновенно пересоберут состояние при повторном посещении экрана.combine для единого результатаconst { data, pending, error } = useQueries({
queries: [{ ...userQuery(id) }, { ...teamQuery(id) }],
combine: results => ({
data: {
user: results[0].data,
team: results[1].data,
},
pending: results.some(r => r.isPending),
error: results.find(r => r.error)?.error,
}),
});
combine вызывается при каждом изменении какого-либо результата, что упрощает мемоизацию.
useMemo.const errors = results.flatMap(r => r.error ? [{ key: r.queryKey, error: r.error }] : []);.combine вернуть null, компонент рендерится как будто данных нет. Удобно скрывать виджеты, пока не загрузилось всё.enabled)const { data: user } = useQuery(userQuery(id));
const projects = useQuery({
...projectsQuery(user?.teamId),
enabled: Boolean(user?.teamId),
});
enabled гарантирует, что второй запрос стартует только после получения параметров из первого. Нет нужды вручную проверять status.
enabled: () => Boolean(user?.teamId) — лениво вычисляется в момент вызова queryFn.retry: false, чтобы зависимый запрос не блокировал UI при повторных падениях.enabled можно передавать queryFn: user ? () => fetchProjects(user.teamId) : skipToken, но булевый флаг читается проще.suspense + зависимые запросыПри использовании useSuspenseQuery комбинируйте с useMemo, чтобы не запускать скрытых запросов:
const projectsQueryOptions = useMemo(() => {
if (!user?.teamId) return undefined;
return projectsQuery(user.teamId);
}, [user?.teamId]);
const projects = useSuspenseQuery(projectsQueryOptions);
suspense: false в зависимом запросе и вручную отображайте skeleton — так не “заморозите” весь экран.placeholderData для зависимых запросов помогает избежать “прыжков” в UI, пока приходит новый teamId.const { data: sessionToken } = useQuery(sessionTokenQuery(), { gcTime: 0 });
const { data: profile } = useQuery({
...profileQuery(),
enabled: Boolean(sessionToken),
meta: { sessionToken },
});
meta можно использовать для проброса результата предыдущей операции во второй queryFn.
meta также удобно логировать, какие зависимые запросы запускались и сколько заняли.useMutation с onSuccess внутри: в onSuccess можете дергать queryClient.ensureQueryData для следующих шагов.await queryClient.fetchQuery({...}) — удобно в роутерах и при серверном пререндере.userId поменялся), первый queryFn отменится автоматически, а второй даже не стартует.Promise.all внутри одного queryFn, если данные должны приходить единовременно.AbortController из queryFn не забывайте прокидывать signal в дочерние fetch’и — TanStack Query сам завершит их при unmount.queryClient.cancelQueries({ queryKey: ['user', id] }) помогает ручным образом остановить первую часть цепочки (например, при закрытии модалки).refetchOnWindowFocus в параллельных запросах можно индивидуально выключать (например, статистика рефрешится, а heavy-репорт — нет).useQueries для получения виджетов параллельно.selectOptionsQuery).enabled: Boolean(selectedId).
useQueries({ queries: ids.map(id => orderQuery(id)) }) + combine для нормализации.useEffect для вызова refetch при смене зависимостей — лучше включайте их в queryKey.useQuery внутри .map. Вынесите в дочерний компонент или аггрегируйте запрос.enabled: isOpen.keepPreviousData: true в зависимых запросах со списками, чтобы не мигала таблица при смене фильтра.select и structuralSharing: false помогают уменьшить количество лишних ререндеров, когда параллельных запросов много.queryClient.prefetchQuery для всех параллельных запросов — клиент сразу получит готовые данные.QueryClient и передавайте defaultOptions: { queries: { retry: false } }, чтобы не ждать повторных попыток.