React Query

React Query — это библиотека для управления асинхронными данными в приложениях на React. Она позволяет работать с серверными данными так же легко, как с локальным состоянием, автоматически кэшируя запросы, обновляя их при изменении данных и обеспечивая оптимистичное обновление интерфейса. Основные концепции включают кэширование, инвалидацию кэша, фоновые обновления и мутирование данных.

  • Кэширование (Caching): все запросы сохраняются в кэше, что позволяет повторно использовать данные без повторных запросов к серверу. Кэш автоматически обновляется при изменении данных.
  • Инвалидация кэша (Cache Invalidation): позволяет помечать данные как устаревшие, чтобы React Query повторно подтянул свежую информацию.
  • Фоновые обновления (Background Refetching): библиотека обновляет кэшированные данные в фоне, обеспечивая актуальность информации без вмешательства пользователя.
  • Мутации (Mutations): операции изменения данных на сервере (POST, PUT, DELETE), которые автоматически синхронизируют кэш после успешного выполнения.

Настройка и интеграция

Для начала работы с React Query необходимо установить пакеты:

npm install @tanstack/react-query

Инициализация происходит через QueryClient и обертку QueryClientProvider, которая должна окружать всё приложение:

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

const queryClient = new QueryClient();

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

QueryClient отвечает за хранение всех запросов, кэшей и настроек по умолчанию.


Использование useQuery

Хук useQuery используется для получения данных. Основной синтаксис:

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

function Todos() {
  const { data, isLoading, error } = useQuery(['todos'], async () => {
    const response = await axios.get('/api/todos');
    return response.data;
  });

  if (isLoading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка загрузки</div>;

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

Ключевые моменты:

  • Первый аргумент — ключ запроса, уникальный идентификатор данных.
  • Второй аргумент — функция запроса, которая возвращает промис с данными.
  • isLoading, error и data — основные состояния запроса.

Дополнительно можно использовать опции, например: staleTime, cacheTime, refetchOnWindowFocus.


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

Иногда нужно обновить данные после мутации. Для этого используется QueryClient.invalidateQueries:

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

function AddTodo() {
  const queryClient = useQueryClient();

  const mutation = useMutation(newTodo => axios.post('/api/todos', newTodo), {
    onSuccess: () => {
      queryClient.invalidateQueries(['todos']);
    }
  });

  const handleAdd = () => {
    mutation.mutate({ title: 'Новая задача' });
  };

  return <button onCl ick={handleAdd}>Добавить</button>;
}

После успешной мутации React Query помечает кэшированные данные с ключом 'todos' устаревшими и повторно их подтягивает.


Использование useMutation

Хук useMutation применяют для операций изменения данных. Основные принципы:

  • Мутация не кэшируется автоматически.
  • Используются коллбеки onSuccess, onError, onSettled для управления состоянием после выполнения запроса.
  • Возможны оптимистичные обновления, когда UI обновляется до завершения запроса.

Пример оптимистичного обновления:

const queryClient = useQueryClient();

const mutation = useMutation(newTodo => axios.post('/api/todos', newTodo), {
  onMutate: async newTodo => {
    await queryClient.cancelQueries(['todos']);
    const previousTodos = queryClient.getQueryData(['todos']);
    queryClient.setQueryData(['todos'], old => [...old, newTodo]);
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    queryClient.invalidateQueries(['todos']);
  },
});

Настройка глобальных параметров

QueryClient позволяет задавать глобальные настройки:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60, // 1 минута
      cacheTime: 1000 * 60 * 5, // 5 минут
      refetchOnWindowFocus: true,
    },
    mutations: {
      retry: 1,
    }
  }
});
  • staleTime — время, когда данные считаются свежими.
  • cacheTime — время хранения данных в кэше после устаревания.
  • refetchOnWindowFocus — автоматический повторный запрос при возврате на вкладку.

Параллельные и последовательные запросы

React Query поддерживает выполнение нескольких запросов одновременно:

const todosQuery = useQuery(['todos'], fetchTodos);
const usersQuery = useQuery(['users'], fetchUsers);

if (todosQuery.isLoading || usersQuery.isLoading) return <div>Загрузка...</div>;

Для зависимых запросов можно использовать опцию enabled:

const userQuery = useQuery(['user', userId], fetchUser, { enabled: !!userId });

Запрос выполняется только если userId существует.


Селекторы и трансформация данных

React Query позволяет использовать select для трансформации данных до передачи их в компонент:

const { data } = useQuery(['todos'], fetchTodos, {
  select: todos => todos.filter(todo => !todo.completed),
});

Это позволяет избегать лишней фильтрации в компоненте и оптимизирует рендер.


Интеграция с Chakra UI

React Query отлично сочетается с Chakra UI:

  • Компоненты загрузки и ошибки: Spinner, Alert.
  • Модальные окна: Modal для подтверждения мутаций.
  • Кнопки и формы: Button, FormControl для взаимодействия с мутациями.

Пример списка задач с Chakra UI:

import { Box, Spinner, Text, List, ListItem } from '@chakra-ui/react';

function Todos() {
  const { data, isLoading, error } = useQuery(['todos'], fetchTodos);

  if (isLoading) return <Spinner />;
  if (error) return <Text color="red.500">Ошибка загрузки</Text>;

  return (
    <List spacing={3}>
      {data.map(todo => (
        <ListItem key={todo.id}>
          <Box p={2} borderWidth={1} borderRadius="md">
            {todo.title}
          </Box>
        </ListItem>
      ))}
    </List>
  );
}

Использование React Query вместе с Chakra UI позволяет создавать интерфейсы, которые динамически обновляются, плавно отображают состояния загрузки и интуитивно реагируют на ошибки.


Полезные хуки и утилиты

  • useQueryClient() — доступ к клиенту для инвалидации кэша и управления состоянием.
  • useInfiniteQuery() — для пагинации и бесконечных списков.
  • useIsFetching() — количество активных запросов, полезно для глобальных индикаторов загрузки.
  • Hydrate — для серверного рендеринга и предзагрузки кэша.

Эта система позволяет строить производительные, отзывчивые и устойчивые к ошибкам приложения, где данные остаются актуальными, а пользовательский интерфейс — плавным и предсказуемым.