Dev Highlights

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

This project is maintained by teniryte

1. Установка и настройка

Быстрый старт

# core + адаптер для React
npm install @tanstack/react-query
# опционально: Devtools (рекомендуется для разработки)
npm install -D @tanstack/react-query-devtools

# альтернативы менеджеров пакетов
pnpm add @tanstack/react-query
pnpm add -D @tanstack/react-query-devtools
yarn add @tanstack/react-query
yarn add --dev @tanstack/react-query-devtools

С версии v5 пакет @tanstack/query-core уже включён транзитивно, поэтому достаточно добавить @tanstack/react-query.

Лайфхак: если у вас монорепозиторий, вынесите @tanstack/react-query в devDependencies корневого пакета и пометьте его как peerDependency для UI-пакетов — так избежите дублирования клиентов при сборке.

Создание QueryClient

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

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60_000,
      gcTime: 5 * 60_000,
      retry: 2,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: 1,
      networkMode: 'online',
    },
  },
  queryCache: new QueryCache({
    onError: (error, query) => {
      console.error('Query error', query.queryKey, error);
    },
  }),
});

Разные пресеты для окружений

const isDev = import.meta.env.DEV;

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: isDev ? 0 : 2,
      refetchOnWindowFocus: !isDev,
      gcTime: isDev ? 5 * 60_000 : 30 * 60_000,
    },
  },
});

Поддержка React Native / Expo

import { Platform } from 'react-native';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // мобильные сети могут быть нестабильными
      retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30_000),
      refetchOnReconnect: Platform.OS !== 'ios', // кастомная стратегия
    },
  },
});

Подключение к React через QueryClientProvider

import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './query-client';

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router />
    </QueryClientProvider>
  );
}

В React 18+ QueryClientProvider должен находиться вне StrictMode или внутри useMemo, если вы создаёте клиент инлайн. Никогда не создавайте new QueryClient() прямо в JSX — это приведёт к потере кеша при каждом рендере.

Хитрость с HMR

const client = globalThis.__queryClient ?? new QueryClient();
if (import.meta.hot) {
  import.meta.hot.accept();
  globalThis.__queryClient = client;
}

Такой паттерн сохраняет кеш между горячими перезагрузками в Vite/Next.js, что ускоряет разработку.

Типовой каркас приложения

import { StrictMode, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const App = () => (
  <div>
    <h1>Dashboard</h1>
    <Todos />
  </div>
);

const Root = () => {
  const [client] = useState(() => new QueryClient());
  return (
    <StrictMode>
      <QueryClientProvider client={client}>
        <App />
      </QueryClientProvider>
    </StrictMode>
  );
};

createRoot(document.getElementById('root')!).render(<Root />);

Разделение клиента для SSR/SPA гибридов

let browserClient: QueryClient | undefined;

export function getQueryClient() {
  if (typeof window === 'undefined') {
    return new QueryClient(); // на сервере создаём новый экземпляр на запрос
  }
  return (browserClient ??= new QueryClient());
}

Этот helper предотвращает утечки памяти на сервере и повторное создание клиента в браузере.

Конфигурация окружения

Типизация ключей и запросов

export const todosQuery = (userId: string) => ({
  queryKey: ['todos', userId] as const,
  queryFn: () => fetchTodos(userId),
});

type TodosQueryKey = ReturnType<typeof todosQuery>['queryKey'];
declare module '@tanstack/react-query' {
  interface Register {
    queryKey: TodosQueryKey;
  }
}

Регистрируя ключи, вы получаете автодополнение и защиту от опечаток.

Настройка Fetch-полифиллов

import 'cross-fetch/polyfill';
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';

Добавьте в точку входа, если поддерживаете IE11/старые Android WebView.

Полезные расширения

Быстрый старт для Next.js (App Router)

// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export function Providers({ children }: { children: React.ReactNode }) {
  const [client] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={client}>
      {children}
      {process.env.NODE_ENV === 'development' && <ReactQueryDevtools />}
    </QueryClientProvider>
  );
}

Expo Router

// app/_layout.tsx
import { Stack } from 'expo-router';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '../src/shared/query-client';

export default function RootLayout() {
  return (
    <QueryClientProvider client={queryClient}>
      <Stack />
    </QueryClientProvider>
  );
}

Интеграция с Zustand/MobX

Храните queryClient в отдельном модуле и импортируйте в сторы/эффекты. Таким образом можно инициировать queryClient.invalidateQueries прямо из Zustand action, не таща React-контекст.

Чек-лист готовности