Инвалидация запросов

Инвалидация запросов в TanStack Query — механизм пометки кешированных данных как устаревших. После инвалидации библиотека понимает, что текущие данные больше не гарантированно актуальны, и инициирует повторное получение информации с сервера.

Инвалидация используется в ситуациях, когда данные на сервере были изменены:

  • после успешной мутации;
  • после изменения сущности другим пользователем;
  • после обновления настроек;
  • после удаления записи;
  • после массовых изменений;
  • после переключения фильтров или контекста приложения.

Главная задача инвалидации — синхронизировать клиентский кеш с серверным состоянием без ручного управления локальными данными.


Проблема устаревших данных

Предположим, приложение получает список задач:

const query = useQuery({
    queryKey: ['tasks'],
    queryFn: fetchTasks
});

После этого пользователь создаёт новую задачу:

await createTask(newTask);

Кеш TanStack Query продолжает содержать старый список, потому что библиотека не знает, что серверные данные изменились.

Без инвалидации пользователь увидит устаревший интерфейс.


QueryClient и управление кешем

Инвалидация выполняется через объект QueryClient.

Обычно доступ к нему получают через хук:

import { useQueryClient } from '@tanstack/react-query';

const queryClient = useQueryClient();

Именно QueryClient управляет:

  • кешем запросов;
  • обновлениями;
  • refetch;
  • удалением данных;
  • инвалидацией;
  • состоянием запросов.

Метод invalidateQueries

Основной инструмент инвалидации:

queryClient.invalidateQueries();

После вызова:

  1. запрос помечается как stale;
  2. активные запросы автоматически перезапрашиваются;
  3. неактивные остаются в кеше, но считаются устаревшими.

Инвалидация конкретного запроса

Наиболее частый сценарий:

queryClient.invalidateQueries({
    queryKey: ['tasks']
});

Теперь все запросы с ключом ['tasks'] становятся устаревшими.


Полный пример после мутации

const queryClient = useQueryClient();

const mutation = useMutation({
    mutationFn: createTask,

    onSuccess: () => {
        queryClient.invalidateQueries({
            queryKey: ['tasks']
        });
    }
});

После успешного создания задачи:

  1. запрос tasks инвалидируется;
  2. TanStack Query выполняет refetch;
  3. список обновляется автоматически.

Что означает stale

В TanStack Query stale — это состояние устаревших данных.

Когда запрос становится stale:

  • кеш остаётся доступным;
  • UI продолжает отображать данные;
  • библиотека понимает, что информация может быть неактуальной;
  • при подходящем событии выполняется refetch.

Инвалидация не удаляет данные из кеша.

Это важное отличие.


Разница между invalidateQueries и refetchQueries

Эти методы похожи, но работают по-разному.

invalidateQueries

queryClient.invalidateQueries({
    queryKey: ['tasks']
});

Что происходит:

  • запрос помечается stale;
  • активные запросы refetch автоматически;
  • неактивные не перезапрашиваются сразу.

refetchQueries

queryClient.refetchQueries({
    queryKey: ['tasks']
});

Что происходит:

  • выполняется немедленный refetch;
  • запрос повторно отправляется независимо от stale-состояния.

Когда использовать invalidateQueries

Инвалидация предпочтительнее в большинстве случаев:

  • после create/update/delete;
  • после изменения профиля;
  • после обновления комментариев;
  • после изменения настроек;
  • после действий администратора.

Преимущество подхода — 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

Для сложной фильтрации используется predicate.

Пример:

queryClient.invalidateQueries({
    predicate: (query) => {
        return query.queryKey[0] === 'tasks';
    }
});

Так можно выбирать запросы динамически.


Работа predicate

В 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'
        );
    }
});

Инвалидация inactive-запросов

По умолчанию TanStack Query повторно запрашивает только активные запросы.

Inactive-запросы:

  • остаются в кеше;
  • помечаются stale;
  • обновятся при следующем использовании.

Это снижает нагрузку на сеть.


Параметр refetchType

Поведение refetch можно настраивать.


active

queryClient.invalidateQueries({
    queryKey: ['tasks'],
    refetchType: 'active'
});

Повторно запрашиваются только активные запросы.

Это поведение по умолчанию.


inactive

queryClient.invalidateQueries({
    queryKey: ['tasks'],
    refetchType: 'inactive'
});

Refetch выполняется только для inactive-запросов.


all

queryClient.invalidateQueries({
    queryKey: ['tasks'],
    refetchType: 'all'
});

Повторно запрашиваются все запросы.


none

queryClient.invalidateQueries({
    queryKey: ['tasks'],
    refetchType: 'none'
});

Запросы лишь помечаются stale без refetch.


Массовая инвалидация

Можно инвалидировать вообще весь кеш:

queryClient.invalidateQueries();

Это делает stale все запросы.

Подобный подход используется редко:

  • logout;
  • глобальный reset;
  • смена аккаунта;
  • переключение workspace.

Инвалидация после удаления сущности

После удаления задачи:

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]
        });
    }
});

invalidateQueries и staleTime

Даже если указан большой staleTime:

useQuery({
    queryKey: ['tasks'],
    queryFn: fetchTasks,
    staleTime: 1000 * 60 * 10
});

Инвалидация всё равно принудительно делает запрос stale.

Это важный механизм ручного контроля актуальности данных.


Инвалидация и optimistic updates

При optimistic update данные сначала обновляются локально.

Позже выполняется серверная мутация.

После успешного ответа обычно вызывается invalidateQueries:

onSettled: () => {
    queryClient.invalidateQueries({
        queryKey: ['tasks']
    });
}

Это гарантирует синхронизацию с реальным серверным состоянием.


Инвалидация через onSuccess

Самый распространённый подход:

const mutation = useMutation({
    mutationFn: createTask,

    onSuccess: () => {
        queryClient.invalidateQueries({
            queryKey: ['tasks']
        });
    }
});

Инвалидация через onSettled

Иногда необходимо обновить данные независимо от результата мутации:

const mutation = useMutation({
    mutationFn: updateTask,

    onSettled: () => {
        queryClient.invalidateQueries({
            queryKey: ['tasks']
        });
    }
});

Инвалидация и background refetch

Во время refetch:

  • старые данные продолжают отображаться;
  • интерфейс не очищается;
  • запрос выполняется в фоне;
  • после завершения UI обновляется.

Это одна из причин высокой отзывчивости TanStack Query.


Поведение при ошибке refetch

Если refetch завершился ошибкой:

  • stale-данные остаются доступными;
  • UI не теряет предыдущую информацию;
  • запрос может быть повторён автоматически.

Такой подход повышает устойчивость интерфейса.


Инвалидация пагинированных запросов

Пример:

['tasks', 1]
['tasks', 2]
['tasks', 3]

Инвалидация:

queryClient.invalidateQueries({
    queryKey: ['tasks']
});

Обновит все страницы пагинации.


Инвалидация infinite queries

Infinite queries работают аналогично.

Пример:

useInfiniteQuery({
    queryKey: ['feed'],
    queryFn: fetchFeed
});

Инвалидация:

queryClient.invalidateQueries({
    queryKey: ['feed']
});

Перезапросит infinite query.


Инвалидация и SSR

При серверном рендеринге invalidateQueries обычно вызывается уже на клиенте после гидрации.

На сервере чаще используются:

  • prefetchQuery;
  • dehydrate;
  • hydrate.

Инвалидация и Devtools

TanStack Query Devtools позволяют наблюдать:

  • stale-состояние;
  • active/inactive queries;
  • refetch;
  • lifecycle запросов;
  • обновление кеша после invalidateQueries.

Это значительно упрощает отладку.


Типичная архитектура инвалидации

Распространённая схема:

const mutation = useMutation({
    mutationFn: apiCall,

    onSuccess: () => {
        queryClient.invalidateQueries({
            queryKey: ['resource']
        });
    }
});

Такой подход:

  • простой;
  • предсказуемый;
  • масштабируемый;
  • легко поддерживается.

Когда инвалидация лучше setQueryData

Инвалидация предпочтительнее, когда:

  • сервер возвращает сложные данные;
  • есть вычисляемые поля;
  • данные зависят от бизнес-логики;
  • сложно синхронизировать локальный кеш вручную;
  • присутствуют связанные сущности.

Когда invalidateQueries может быть избыточным

Иногда полный refetch слишком дорогой:

  • большие списки;
  • тяжёлые API;
  • медленные запросы;
  • частые обновления.

В таких случаях используют:

queryClient.setQueryData()

для локального обновления кеша без повторного запроса.


Комбинирование setQueryData и invalidateQueries

Частая стратегия:

  1. мгновенно обновить UI через setQueryData;
  2. затем выполнить invalidateQueries для синхронизации.

Пример:

onSuccess: (newTask) => {
    queryClient.setQueryData(
        ['tasks'],
        (old) => [...old, newTask]
    );

    queryClient.invalidateQueries({
        queryKey: ['tasks']
    });
}

Ошибки при использовании invalidateQueries

Слишком широкие queryKey

Плохо:

queryClient.invalidateQueries({
    queryKey: ['user']
});

Если приложение содержит:

['user']
['user', 'settings']
['user', 'posts']
['user', 'notifications']

будут обновлены все запросы.


Чрезмерный refetch

Частая ошибка — инвалидировать данные после каждой мелкой операции.

Это приводит к:

  • лишнему сетевому трафику;
  • скачкам UI;
  • нагрузке на сервер;
  • ухудшению производительности.

Неправильная структура queryKey

Если ключи организованы хаотично:

['tasks']
['task-list']
['allTasks']

инвалидация становится непредсказуемой.


Рекомендуемая структура queryKey

Хорошая структура:

['tasks']
['tasks', 'list']
['tasks', 'detail', id]
['tasks', 'stats']

Так проще:

  • группировать запросы;
  • инвалидировать данные;
  • управлять refetch;
  • поддерживать кодовую базу.

Стратегии инвалидации

Грубая инвалидация

invalidateQueries(['tasks'])

Простая, но может создавать лишний refetch.


Точечная инвалидация

invalidateQueries(['task', id])

Более производительная стратегия.


Гибридный подход

Инвалидируется:

  • конкретная сущность;
  • связанные списки.

Это наиболее распространённая архитектура в крупных приложениях.


Роль инвалидации в Server State

TanStack Query рассматривает серверные данные как временный снимок состояния.

invalidateQueries сообщает библиотеке:

локальная копия больше не гарантированно соответствует серверу.

После этого TanStack Query самостоятельно восстанавливает консистентность кеша.