Доки по разработке
This project is maintained by teniryte
Инвалидация — основной способ сообщить TanStack Query, что данные устарели и их нужно перезагрузить. Фоновые обновления гарантируют, что пользователь видит свежую информацию без заметных пауз.
Быстрые ориентиры
staleTime «длинным», а gcTime — коротким: свежие данные на экране, мусор в кешe не накапливается.predicate), а не весь кеш — дешевле и предсказуемее.setQueryData + invalidateQueries: мгновенный UI и гарантированный рефетч в фоне.cancelRefetch: true, если данные и так перезатираются стримом (WebSocket/SSE).ensureQueryData + invalidateQueries внутри router.refresh(), чтобы сервер и клиент оставались синхронными.invalidateQueriesconst queryClient = useQueryClient();
await queryClient.invalidateQueries({
queryKey: ['todos', { userId }],
});
isInvalidated и запускает рефетч для всех активных наблюдателей с подходящим ключом.cancelRefetch: true, чтобы не прерывать уже запущенный запрос (например, пользователь вручную нажал Refresh).predicateawait queryClient.invalidateQueries({
predicate: query =>
query.queryKey[0] === 'todos' &&
query.state.data?.ownerId === userId &&
query.state.fetchStatus === 'idle',
});
predicate получает Query и позволяет фильтровать не только по ключам, но и по любым частям состояния.stale: false, чтобы «пнуть» только свежие записи и форсировать рефетч даже без истечения staleTime.queryClient.invalidateQueries({
queryKey: ['todos'],
exact: false, // default
refetchType: 'active', // 'active' | 'inactive' | 'all'
});
active обновляет только запросы с подписчиками.inactive — только кешированные, без активных компонентов.all — оба варианта.type: 'all' | 'active' | 'inactive' в v5 можно комбинировать с refetchType, чтобы сначала отметить кеш несвежим, а потом решить, кого рефетчить.onSuccess мутации, но рефетч запускайте в onSettled, чтобы не ломать rollbacks.refetchconst { refetch } = useQuery(todoQuery(id));
await refetch({ throwOnError: true, cancelRefetch: false });
refetch игнорирует staleTime и enabled, но уважает retry.
suspense: true оборачивайте refetch в startTransition, иначе UI «подвиснет» до завершения запроса.refetch полезен для «Pull to refresh» — отображайте isFetching вместо isLoading, чтобы не мигал весь список.staleTime)staleTime управляет тем, когда запрос считается устаревшим. Пока данные «свежие», автоматические триггеры (фокус, reconnect) их не перезагружают.
useQuery({
queryKey: ['feed'],
queryFn: fetchFeed,
staleTime: 2 * 60_000,
refetchOnWindowFocus: true,
});
Пользователь увидит мгновенный UI, а свежие данные подгрузятся, когда staleTime истечёт.
staleTime: Infinity + ручную инвалидацию по событию (например, когда приходит пуш-уведомление) — так список не дергается сам по себе.gcTime: 5 * 60_000, чтобы данные удалялись из памяти уже через 5 минут простоя, но оставались «вечнозелёными», пока пользователь на странице.await queryClient.prefetchQuery({
queryKey: ['feed', page],
queryFn: () => fetchFeed(page),
staleTime: 30_000,
});
const data = await queryClient.ensureQueryData({
queryKey: ['feed', page],
queryFn: () => fetchFeed(page),
});
prefetchQuery хорошо вызывать в маршрутизаторе (React Router loaders / Next server actions), чтобы экран сразу показывал кеш.ensureQueryData в v5 может вернуть уже загруженные данные или дождаться активного запроса — удобно в server components.prefetchQuery добавьте queryClient.invalidateQueries при событии, которое меняет данные, чтобы закешированный предзагруженный экран пересчитался в фоне.refetchOnWindowFocus — true по умолчанию. Для десктопных приложений с тяжелыми запросами установите false в defaultOptions.refetchOnReconnect — рефетчит, когда onlineManager сообщает о восстановлении сети.refetchInterval — периодический опрос. Возвращайте false, чтобы временно отключить.useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
refetchInterval: data => (data?.hasUnread ? 15_000 : false),
});
refetchOnMount: 'always' заставит компонент перезагружать данные каждый раз при повторном маунте, даже если кеш свежий — полезно для очень критичных экранов.networkMode: 'offlineFirst', если хотите, чтобы фокус/реконнект не запускал рефетч без сети: запрос автоматически «паркуется» до появления соединения.focusManager.setEventListener, чтобы реагировать на state AppState.const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async newTodo => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const prev = queryClient.getQueryData(['todos']);
queryClient.setQueryData(['todos'], old =>
old?.map(todo => (todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)),
);
return { prev };
},
onError: (_err, _newTodo, ctx) => {
if (ctx?.prev) queryClient.setQueryData(['todos'], ctx.prev);
},
onSettled: (_, __, variables) => {
queryClient.invalidateQueries({
queryKey: ['todos', { userId: variables.userId }],
refetchType: 'active',
});
},
});
cancelQueries перед оптимистичным апдейтом гарантирует, что старый рефетч не перезапишет новый state.onSettled срабатывает и после ошибок, чтобы запрос вернулся в консистентное состояние (например, сервер откатил изменения).queryClient.setQueryData(['todo', id], data, { updatedAt: Date.now() }), а затем инвалидировать коллекцию (['todos']), чтобы список обновился.setQueryData и маркировка свежестиqueryClient.setQueryData(['todo', id], updater, {
updatedAt: Date.now(),
});
Если данные поступили из другого источника (WebSocket), отметьте их «свежими», чтобы избежать немедленного рефетча.
updatedAt можно проставлять заранее (например, timestamp из backend). Главное — чтобы все клиенты использовали одну эпоху (UTC), иначе «свежесть» будет прыгать.structuralSharing: true (по умолчанию) не перерисует компоненты, если вернёте тот же объект/массив. Используйте immer или cloneDeep, если хотите гарантированно поменять ссылки.fetchStatusqueryClient.invalidateQueries({
queryKey: ['todos'],
refetchType: 'inactive',
});
queryClient.refetchQueries({
queryKey: ['todos'],
type: 'active',
});
Так можно развести обновление фонового кеша и данных на экране.
queryClient.isFetching({ queryKey: ['todos'] }) помогает показывать глобальный спиннер «данные актуализируются», даже если пользователь на другой вкладке.fetchStatus: 'paused' возникает, когда сеть офлайн и networkMode: 'online'. Можно слушать это состояние и показывать «Работаем офлайн».queryClient.getQueryCache().subscribe(event => {
if (event?.type === 'queryUpdated') {
console.log(event.query.queryKey, event.action?.type);
}
});
Подходит для аналитики или логирования флапающих запросов.
event.action?.type === 'success' | 'error' | 'failed', чтобы собирать метрики по конкретным ключам (например, сколько раз подряд падает ['payments']).showQueryKeys — удобно визуализировать, какие ключи инвалидируются чаще остальных.queryClient.batch() (или просто последовательные вызовы внутри unstable_batchedUpdates).window.location.reload() для обновления данных — TanStack Query решит все проблемы на уровне кеша.queryClient.clear() + queryClient.invalidateQueries() после повторной авторизации — так не вытечет приватный кеш.testQueryClient helper (из @tanstack/react-query-testing) — тесты будут детерминированными.