При разработке приложения учёта расходов нам всё чаще нужно работать с API: получать список транзакций, отправлять новые данные, обновлять или удалять их. Если делать это вручную через useEffect, useState и Axios — код становится громоздким и повторяющимся.
Решение — React Query (а точнее — TanStack Query). Это мощная библиотека, которая:Решение — React Query (а точнее — TanStack Query). Это мощная библиотека, которая:
- управляет асинхронными запросами;
- кэширует ответы;
- автоматически повторяет неудачные запросы;
- упрощает логику загрузки, ошибок и обновлений данных.
Установка и настройка React Query
Установка библиотек
Выполним установку двух библиотек: основного пакета и DevTools для отладки.
npm install @tanstack/react-query @tanstack/react-query-devtools
Убедитесь, что Axios тоже установлен:
npm install axios
Создание клиента QueryClient
Каждое приложение на React Query работает с QueryClient — это хранилище для кэша, настроек и состояний запросов.
Создадим файл с его настройками. Файл src/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 2, // Повторить запрос максимум 2 раза при ошибке
refetchOnWindowFocus: false, // Не делать автозапрос при возвращении в окно
},
},
});
export default queryClient;
Подключение провайдера
Подключим QueryClientProvider в корневом файле, чтобы все компоненты могли использовать React Query.
Файл src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import queryClient from './queryClient';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>
);
Теперь всё готово для использования React Query во всех частях приложения.
Получение данных с useQuery
Начнём с самого простого — загрузки данных с сервера.
Представим, что у нас есть API-эндпоинт /transactions, который возвращает список всех транзакций пользователя. Мы уже умеем писать такие сервисы через Axios.
Файл src/services/transactionService.ts
import api from './api';
export const fetchTransactions = async () => {
const response = await api.get('/transactions');
return response.data;
};
Теперь создадим компонент, в котором используем useQuery для загрузки данных:
Файл src/components/TransactionList.tsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { fetchTransactions } from '../services/transactionService';
const TransactionList: React.FC = () => {
const { data: transactions, isLoading, isError } = useQuery(['transactions'], fetchTransactions);
if (isLoading) return <p>Загрузка транзакций...</p>;
if (isError) return <p>Не удалось загрузить данные.</p>;
return (
<ul>
{transactions.map((tx: any) => (
<li key={tx.id}>
{tx.date.slice(0, 10)} — {tx.category}: {tx.type === 'income' ? '+' : '-'}${tx.amount}
</li>
))}
</ul>
);
};
export default TransactionList;
useQuery делает запрос при монтировании компонента.
- Что здесь происходит:
- Ключ запроса —
['transactions'], он нужен для кэширования и обновления. - В ответ мы получаем:
data— массив транзакций;isLoading— флаг загрузки;isError— флаг ошибки.
Отправка данных с useMutation
Чтобы пользователь мог добавлять новые транзакции, мы будем использовать useMutation.
Шаг 1. Создаём функцию для отправки транзакции
Файл src/services/transactionService.ts
export const createTransaction = async (transaction: Omit<Transaction, 'id'>) => {
const response = await api.post('/transactions', transaction);
return response.data;
};
Шаг 2. Используем useMutation в компоненте
Файл src/components/AddTransactionForm.tsx
import React, { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTransaction } from '../services/transactionService';
const AddTransactionForm: React.FC = () => {
const [amount, setAmount] = useState('');
const [category, setCategory] = useState('');
const [type, setType] = useState<'income' | 'expense'>('expense');
const queryClient = useQueryClient();
const mutation = useMutation(createTransaction, {
onSuccess: () => {
// После успешного запроса обновляем список
queryClient.invalidateQueries(['transactions']);
},
});
const handleSubmit = () => {
mutation.mutate({
category,
amount: parseFloat(amount),
type,
date: new Date().toISOString(),
});
setAmount('');
setCategory('');
};
return (
<div>
<h3>Добавить транзакцию</h3>
<input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="Сумма" />
<input value={category} onChange={(e) => setCategory(e.target.value)} placeholder="Категория" />
<select value={type} onChange={(e) => setType(e.target.value as 'income' | 'expense')}>
<option value="income">Доход</option>
<option value="expense">Расход</option>
</select>
<button onClick={handleSubmit}>Добавить</button>
</div>
);
};
export default AddTransactionForm;
Объяснение:
useMutationиспользуется для отправки данных (POST-запрос).- После успешного выполнения
onSuccessобновляет кэш, чтобы список транзакций стал актуальным. queryClient.invalidateQueries(['transactions'])заставляет React Query заново загрузить данные.
Обновление и ручная подгрузка
React Query поддерживает ручное обновление данных:
const { data, refetch } = useQuery(['transactions'], fetchTransactions);
// В интерфейсе:
<button onClick={() => refetch()}>Обновить список</button>
Подключение React Query Devtools
Для отладки очень удобно использовать Devtools — панель, которая показывает статус всех запросов, кэш и прочую информацию.
Добавьте ReactQueryDevtools внутрь QueryClientProvider:
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
Теперь при запуске приложения у вас будет интерфейс для анализа запросов.
Типичные ошибки и рекомендации
Частая ошибка при использовании React Query — забывать обновлять кэш после изменения данных. Например, при добавлении, удалении или изменении транзакций не забывайте вызывать queryClient.invalidateQueries.
Ещё одна популярная ошибка — неправильное назначение ключей запросов. Ключи должны быть уникальными для каждого типа данных. Например, для списка транзакций используйте ['transactions'], а для одной конкретной задачи — ['transaction', id].
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ