Dev Highlights

Доки по разработке

This project is maintained by teniryte

7. Инвалидация и фоновые обновления

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

Быстрые ориентиры

invalidateQueries

const queryClient = useQueryClient();

await queryClient.invalidateQueries({
  queryKey: ['todos', { userId }],
});

Селективная инвалидация через predicate

await queryClient.invalidateQueries({
  predicate: query =>
    query.queryKey[0] === 'todos' &&
    query.state.data?.ownerId === userId &&
    query.state.fetchStatus === 'idle',
});

Тонкая настройка

queryClient.invalidateQueries({
  queryKey: ['todos'],
  exact: false,       // default
  refetchType: 'active', // 'active' | 'inactive' | 'all'
});

Явный refetch

const { refetch } = useQuery(todoQuery(id));
await refetch({ throwOnError: true, cancelRefetch: false });

refetch игнорирует staleTime и enabled, но уважает retry.

Фоновое обновление (staleTime)

staleTime управляет тем, когда запрос считается устаревшим. Пока данные «свежие», автоматические триггеры (фокус, reconnect) их не перезагружают.

useQuery({
  queryKey: ['feed'],
  queryFn: fetchFeed,
  staleTime: 2 * 60_000,
  refetchOnWindowFocus: true,
});

Пользователь увидит мгновенный UI, а свежие данные подгрузятся, когда staleTime истечёт.

Предзагрузка и фоновые апдейты

await queryClient.prefetchQuery({
  queryKey: ['feed', page],
  queryFn: () => fetchFeed(page),
  staleTime: 30_000,
});

const data = await queryClient.ensureQueryData({
  queryKey: ['feed', page],
  queryFn: () => fetchFeed(page),
});

Автоматические триггеры

useQuery({
  queryKey: ['notifications'],
  queryFn: fetchNotifications,
  refetchInterval: data => (data?.hasUnread ? 15_000 : false),
});

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

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

setQueryData и маркировка свежести

queryClient.setQueryData(['todo', id], updater, {
  updatedAt: Date.now(),
});

Если данные поступили из другого источника (WebSocket), отметьте их «свежими», чтобы избежать немедленного рефетча.

Ручное управление fetchStatus

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

queryClient.refetchQueries({
  queryKey: ['todos'],
  type: 'active',
});

Так можно развести обновление фонового кеша и данных на экране.

Подписка на события

queryClient.getQueryCache().subscribe(event => {
  if (event?.type === 'queryUpdated') {
    console.log(event.query.queryKey, event.action?.type);
  }
});

Подходит для аналитики или логирования флапающих запросов.

Best practices