Инвалидация запросов в TanStack Query — механизм пометки кешированных данных как устаревших. После инвалидации библиотека понимает, что текущие данные больше не гарантированно актуальны, и инициирует повторное получение информации с сервера.
Инвалидация используется в ситуациях, когда данные на сервере были изменены:
Главная задача инвалидации — синхронизировать клиентский кеш с серверным состоянием без ручного управления локальными данными.
Предположим, приложение получает список задач:
const query = useQuery({
queryKey: ['tasks'],
queryFn: fetchTasks
});
После этого пользователь создаёт новую задачу:
await createTask(newTask);
Кеш TanStack Query продолжает содержать старый список, потому что библиотека не знает, что серверные данные изменились.
Без инвалидации пользователь увидит устаревший интерфейс.
Инвалидация выполняется через объект QueryClient.
Обычно доступ к нему получают через хук:
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
Именно QueryClient управляет:
Основной инструмент инвалидации:
queryClient.invalidateQueries();
После вызова:
Наиболее частый сценарий:
queryClient.invalidateQueries({
queryKey: ['tasks']
});
Теперь все запросы с ключом ['tasks'] становятся
устаревшими.
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createTask,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
}
});
После успешного создания задачи:
tasks инвалидируется;В TanStack Query stale — это состояние устаревших данных.
Когда запрос становится stale:
Инвалидация не удаляет данные из кеша.
Это важное отличие.
Эти методы похожи, но работают по-разному.
queryClient.invalidateQueries({
queryKey: ['tasks']
});
Что происходит:
queryClient.refetchQueries({
queryKey: ['tasks']
});
Что происходит:
Инвалидация предпочтительнее в большинстве случаев:
Преимущество подхода — TanStack Query самостоятельно управляет жизненным циклом запросов.
TanStack Query поддерживает иерархические queryKey.
Пример:
['tasks']
['tasks', 'list']
['tasks', 'detail', 15]
Инвалидация родительского ключа:
queryClient.invalidateQueries({
queryKey: ['tasks']
});
Инвалидирует:
['tasks']
['tasks', 'list']
['tasks', 'detail', 15]
Это позволяет обновлять целые группы запросов.
Иногда необходимо инвалидировать только один запрос.
Для этого используется exact:
queryClient.invalidateQueries({
queryKey: ['tasks'],
exact: true
});
Теперь будут затронуты только:
['tasks']
Но не:
['tasks', 'list']
Пример запроса:
useQuery({
queryKey: ['task', taskId],
queryFn: () => fetchTask(taskId)
});
После обновления задачи:
queryClient.invalidateQueries({
queryKey: ['task', taskId]
});
Перезапросится только конкретная сущность.
После изменения задачи часто необходимо обновить:
Пример:
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
queryClient.invalidateQueries({
queryKey: ['task', variables.id]
});
}
После создания комментария могут измениться:
Пример:
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['comments']
});
queryClient.invalidateQueries({
queryKey: ['notifications']
});
queryClient.invalidateQueries({
queryKey: ['stats']
});
}
Для сложной фильтрации используется predicate.
Пример:
queryClient.invalidateQueries({
predicate: (query) => {
return query.queryKey[0] === 'tasks';
}
});
Так можно выбирать запросы динамически.
В predicate передаётся объект Query.
Пример структуры:
query.queryKey
query.state
query.meta
query.options
Это позволяет строить сложные условия.
Пример:
['tasks', { status: 'done' }]
['tasks', { status: 'active' }]
Инвалидировать только completed-задачи:
queryClient.invalidateQueries({
predicate: (query) => {
return (
query.queryKey[0] === 'tasks' &&
query.queryKey[1]?.status === 'done'
);
}
});
По умолчанию TanStack Query повторно запрашивает только активные запросы.
Inactive-запросы:
Это снижает нагрузку на сеть.
Поведение refetch можно настраивать.
queryClient.invalidateQueries({
queryKey: ['tasks'],
refetchType: 'active'
});
Повторно запрашиваются только активные запросы.
Это поведение по умолчанию.
queryClient.invalidateQueries({
queryKey: ['tasks'],
refetchType: 'inactive'
});
Refetch выполняется только для inactive-запросов.
queryClient.invalidateQueries({
queryKey: ['tasks'],
refetchType: 'all'
});
Повторно запрашиваются все запросы.
queryClient.invalidateQueries({
queryKey: ['tasks'],
refetchType: 'none'
});
Запросы лишь помечаются stale без refetch.
Можно инвалидировать вообще весь кеш:
queryClient.invalidateQueries();
Это делает stale все запросы.
Подобный подход используется редко:
После удаления задачи:
const mutation = useMutation({
mutationFn: deleteTask,
onSuccess: (_, taskId) => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
queryClient.invalidateQueries({
queryKey: ['task', taskId]
});
}
});
Это предотвращает отображение удалённых данных.
const mutation = useMutation({
mutationFn: updateTask,
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ['task', variables.id]
});
}
});
Даже если указан большой staleTime:
useQuery({
queryKey: ['tasks'],
queryFn: fetchTasks,
staleTime: 1000 * 60 * 10
});
Инвалидация всё равно принудительно делает запрос stale.
Это важный механизм ручного контроля актуальности данных.
При optimistic update данные сначала обновляются локально.
Позже выполняется серверная мутация.
После успешного ответа обычно вызывается invalidateQueries:
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
}
Это гарантирует синхронизацию с реальным серверным состоянием.
Самый распространённый подход:
const mutation = useMutation({
mutationFn: createTask,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
}
});
Иногда необходимо обновить данные независимо от результата мутации:
const mutation = useMutation({
mutationFn: updateTask,
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['tasks']
});
}
});
Во время refetch:
Это одна из причин высокой отзывчивости TanStack Query.
Если refetch завершился ошибкой:
Такой подход повышает устойчивость интерфейса.
Пример:
['tasks', 1]
['tasks', 2]
['tasks', 3]
Инвалидация:
queryClient.invalidateQueries({
queryKey: ['tasks']
});
Обновит все страницы пагинации.
Infinite queries работают аналогично.
Пример:
useInfiniteQuery({
queryKey: ['feed'],
queryFn: fetchFeed
});
Инвалидация:
queryClient.invalidateQueries({
queryKey: ['feed']
});
Перезапросит infinite query.
При серверном рендеринге invalidateQueries обычно вызывается уже на клиенте после гидрации.
На сервере чаще используются:
TanStack Query Devtools позволяют наблюдать:
Это значительно упрощает отладку.
Распространённая схема:
const mutation = useMutation({
mutationFn: apiCall,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['resource']
});
}
});
Такой подход:
Инвалидация предпочтительнее, когда:
Иногда полный refetch слишком дорогой:
В таких случаях используют:
queryClient.setQueryData()
для локального обновления кеша без повторного запроса.
Частая стратегия:
Пример:
onSuccess: (newTask) => {
queryClient.setQueryData(
['tasks'],
(old) => [...old, newTask]
);
queryClient.invalidateQueries({
queryKey: ['tasks']
});
}
Плохо:
queryClient.invalidateQueries({
queryKey: ['user']
});
Если приложение содержит:
['user']
['user', 'settings']
['user', 'posts']
['user', 'notifications']
будут обновлены все запросы.
Частая ошибка — инвалидировать данные после каждой мелкой операции.
Это приводит к:
Если ключи организованы хаотично:
['tasks']
['task-list']
['allTasks']
инвалидация становится непредсказуемой.
Хорошая структура:
['tasks']
['tasks', 'list']
['tasks', 'detail', id]
['tasks', 'stats']
Так проще:
invalidateQueries(['tasks'])
Простая, но может создавать лишний refetch.
invalidateQueries(['task', id])
Более производительная стратегия.
Инвалидируется:
Это наиболее распространённая архитектура в крупных приложениях.
TanStack Query рассматривает серверные данные как временный снимок состояния.
invalidateQueries сообщает библиотеке:
локальная копия больше не гарантированно соответствует серверу.
После этого TanStack Query самостоятельно восстанавливает консистентность кеша.