Кэширование API с помощью React Query: кейс из реального проекта
Автор: Javohir Abdullayev · · React и Frontend

Введение: Головная боль с API-запросами в крупном проекте
Каждый фронтенд-разработчик хотя бы раз в своей практике сталкивался со следующей проблемой: при переходе пользователя между страницами одни и те же данные повторно загружаются с сервера, количество спиннеров загрузки растет, а нагрузка на сервер увеличивается. В этой статье мы рассмотрим, как решили подобную проблему в реальном проекте панели управления электронной коммерцией (admin dashboard) с помощью TanStack Query (React Query).
Контекст проекта и первоначальная проблема
В разработанной нами CRM-системе были список клиентов, статистика заказов и каталог товаров. Все данные загружались классическим способом через useEffect и useState:
// Устаревший ошибочный подход
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/products')
.then(res => res.json())
.then(data => {
setProducts(data);
setLoading(false);
});
}, []);Сначала казалось, что этот подход работает нормально, однако по мере роста проекта возникли серьезные проблемы:
- Ненужные повторные запросы (Duplicate requests): Каждый раз, когда пользователь перемещался между страницами (например, Товары → Настройки → Товары), весь список запрашивался заново, вызывая мерцание экрана (flicker).
- Устаревшие данные (Stale data): При одновременной работе нескольких администраторов изменение цены одним админом не обновлялось на экране другого.
- Утечки памяти и неотмененные запросы: При размонтировании компонента запрос продолжал выполняться, вызывая предупреждения в консоли.
- Путаница в глобальном состоянии: Использование Redux только для хранения данных с сервера перегружало кодовую базу лишним бойлерплейтом.
Решение: Интеграция TanStack Query
Для решения проблемы мы перешли на библиотеку @tanstack/react-query. Она берет на себя управление серверным состоянием (server state), кэширование, синхронизацию и фоновое обновление данных в автоматическом режиме.
1. Настройка QueryClient
Сначала в основном файле проекта мы правильно настроили время жизни кэша (staleTime и gcTime):
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // Данные считаются свежими в течение 5 минут
gcTime: 1000 * 60 * 10, // Хранятся в кэше 10 минут
refetchOnWindowFocus: false,
retry: 1,
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<DashboardLayout />
</QueryClientProvider>
);
}2. Создание кастомного хука (Custom Hook)
Чтобы сделать вызовы API чистыми и переиспользуемыми, мы написали специальный хук:
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
const fetchProducts = async (page = 1) => {
const { data } = await axios.get(`/api/products?page=${page}`);
return data;
};
export const useProducts = (page) => {
return useQuery({
queryKey: ['products', page],
queryFn: () => fetchProducts(page),
placeholderData: (previousData) => previousData, // плавный переход для пагинации
});
};3. Обновление данных и оптимистичные обновления (Optimistic Updates)
Мгновенное отображение изменений в интерфейсе (Optimistic Update) при редактировании цены товара пользователем, не дожидаясь ответа сервера, существенно улучшило пользовательский опыт (UX):
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
export const useUpdateProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (updatedProduct) => axios.put(`/api/products/${updatedProduct.id}`, updatedProduct),
onMutate: async (newProduct) => {
await queryClient.cancelQueries({ queryKey: ['products'] });
const previousProducts = queryClient.getQueryData(['products']);
queryClient.setQueryData(['products'], (old) =>
old?.map(item => item.id === newProduct.id ? { ...item, ...newProduct } : item)
);
return { previousProducts };
},
onError: (err, newProduct, context) => {
queryClient.setQueryData(['products'], context.previousProducts);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
};Результаты: Какие изменения произошли в проекте?
После полного перехода на React Query мы достигли следующих конкретных показателей в нашем проекте:
- Нагрузка на сервер снизилась на 65%: Благодаря правильной настройке staleTime лишние повторные запросы от одного и того же пользователя практически полностью исчезли.
- Объем Redux сократился на 70%: В Redux осталось только чисто клиентское состояние (например, состояние сайдбара, выбор темы). Все состояния API были переданы под управление React Query.
- Улучшилась скорость приложения и UX: Переходы между страницами стали мгновенными, так как данные читаются напрямую из кэша и обновляются в фоновом режиме.
Заключение и рекомендации
Если в вашем React-проекте много API-запросов и вы устали загружать данные внутри useEffect, обязательно используйте TanStack Query. Он не только сократит ваш код, но и сделает приложение в разы быстрее для пользователей.
Теги: #React #TanStack Query #Frontend #Optimizatsiya #API