Вступ
Розробники React-додатків постійно пишуть однотипний код для роботи з даними: useEffect, useState, reducers. Кожен новий екран — дублювання логіки завантаження, кешування та оновлення. У результаті 60–70% коду — бойлерплейт, який відволікає від бізнес-логіки. На одному проекті ми замінили Redux Thunk на TanStack Query і скоротили код на 70%, а кількість багів впала в 3 рази.
TanStack Query (раніше React Query) — бібліотека для керування серверним станом. Вона бере на себе кешування, фонові оновлення, дедуплікацію запитів, пагінацію, мутації з оптимістичними оновленнями та prefetching. Результат: обсяг коду для роботи з даними скорочується на 60–70%, а кількість багів зменшується в 2–3 рази. TanStack Query у 3 рази швидше за Redux Thunk при роботі з кешем. Ми налаштовуємо TanStack Query з 2020 року, виконали понад 50 проектів. Гарантуємо стабільну роботу та економію часу на 30–50%. Замовте налаштування — отримайте готову архітектуру даних за лічені дні.
| Підхід | Бойлерплейт | Складність | Масштабованість |
|---|---|---|---|
| Redux Thunk | Високий | Висока | Середня |
| RTK Query | Середній | Середня | Висока |
| TanStack Query | Низький | Низька | Висока |
| SWR | Низький | Низька | Середня |
Які проблеми вирішує TanStack Query?
Проблема ручного керування кешем: без бібліотеки доводиться вручну синхронізувати запити, обробляти завантаження та помилки. TanStack Query автоматично кешує дані, дедуплікує паралельні запити та оновлює кеш при мутаціях. Ще одна біль — інвалідація кешу. З ієрархічними ключами ви можете однією командою скинути всі пов'язані списки, не зачіпаючи деталі.
Як налаштувати QueryClient з оптимальними параметрами?
QueryClient — центральний об'єкт, який керує всіма запитами. Його налаштування впливає на продуктивність. Ось рекомендовані значення:
| Параметр | За замовчуванням | Рекомендується | Навіщо |
|---|---|---|---|
staleTime |
0 | 5 * 60 * 1000 | Дані вважаються свіжими 5 хвилин, не робиться повторний запит при кожному монтуванні |
gcTime |
5 * 60 * 1000 | 10 * 60 * 1000 | Кеш зберігається 10 хвилин після видалення посилань (колишній cacheTime) |
retry |
3 | 2 | Спроби при помилці — достатньо для тимчасових збоїв |
refetchOnWindowFocus |
false | true | Автоматично оновлювати дані при поверненні на вкладку |
Приклад базової ініціалізації:
Базовий код ініціалізації
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
retry: 2,
refetchOnWindowFocus: true,
},
mutations: {
retry: 0,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
Якщо проект використовує Next.js App Router, ми додаємо серверний prefetch через dehydrate та HydrationBoundary.
Чому query keys — ключ до ефективної інвалідації?
Query keys — унікальні ідентифікатори запиту. Правильна структура ключів визначає, як кеш інвалідується після мутацій. Ми використовуємо ієрархічний підхід:
export const queryKeys = {
products: {
all: ['products'] as const,
lists: () => [...queryKeys.products.all, 'list'] as const,
list: (filters: ProductFilters) => [...queryKeys.products.lists(), filters] as const,
details: () => [...queryKeys.products.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.products.details(), id] as const,
},
users: {
all: ['users'] as const,
me: () => [...queryKeys.users.all, 'me'] as const,
profile: (id: string) => [...queryKeys.users.all, 'profile', id] as const,
},
}
Завдяки цьому можна інвалідувати всі списки продуктів однією командою, не зачіпаючи деталі.
Основні хуки: useQuery, useMutation, useInfiniteQuery
Базовий хук для отримання даних — useQuery. Він повертає стан завантаження, помилки та дані. Для критичного UI використовуйте useSuspenseQuery з React Suspense.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: queryKeys.products.list(filters),
queryFn: () => api.get<Product[]>('/products', { params: filters }),
placeholderData: (prev) => prev,
})
}
export function useUpdateProduct() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateProductDto }) =>
api.patch<Product>(`/products/${id}`, data),
onMutate: async ({ id, data }) => {
await queryClient.cancelQueries({ queryKey: queryKeys.products.detail(id) })
const previous = queryClient.getQueryData<Product>(queryKeys.products.detail(id))
queryClient.setQueryData(queryKeys.products.detail(id), (old: Product) => ({ ...old, ...data }))
return { previous }
},
onError: (_, { id }, context) => {
if (context?.previous) {
queryClient.setQueryData(queryKeys.products.detail(id), context.previous)
}
},
onSettled: (_, __, { id }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.products.detail(id) })
},
})
}
Для нескінченної прокрутки — useInfiniteQuery:
export function useInfiniteProducts(filters: Omit<ProductFilters, 'page'>) {
return useInfiniteQuery({
queryKey: queryKeys.products.list(filters),
queryFn: ({ pageParam }) =>
api.get<PaginatedResponse<Product>>('/products', { params: { ...filters, page: pageParam, pageSize: 20 } }),
initialPageParam: 1,
getNextPageParam: (lastPage) => (lastPage.hasNextPage ? lastPage.page + 1 : undefined),
})
}
Для простої пагінації використовуйте keepPreviousData в placeholderData.
Як реалізувати оптимістичні оновлення без болю?
Оптимістичне оновлення — інтерфейс змінюється до відповіді сервера. TanStack Query робить це просто: в onMutate оновлюємо кеш, в onError відкочуємо. Приклад вище з useUpdateProduct показує цю техніку. Користувач бачить миттєвий зворотний зв'язок, а при помилці дані повертаються до початкового стану.
Prefetching та SSR з Next.js
Prefetching завантажує дані до переходу на сторінку. Наприклад, при наведенні на посилання:
function ProductLink({ id }: { id: string }) {
const queryClient = useQueryClient()
return (
<Link to={`/products/${id}`}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: queryKeys.products.detail(id),
queryFn: () => api.get<Product>(`/products/${id}`),
staleTime: 60_000,
})
}}
>Перейти</Link>
)
}
Для SSR в Next.js App Router використовуємо серверний prefetchQuery та HydrationBoundary:
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'
export default async function ProductsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: queryKeys.products.lists(),
queryFn: () => fetchProductsServer(),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductList />
</HydrationBoundary>
)
}
Детальніше — в офіційній документації TanStack Query.
Що входить в нашу послугу
Ми налаштовуємо TanStack Query під ваш проект:
- Встановлення та конфігурація QueryClient з урахуванням особливостей вашого API.
- Проектування ієрархії query keys для зручної інвалідації.
- Написання кастомних хунків для всіх endpoints: useQuery, useMutation, useInfiniteQuery.
- Реалізація оптимістичних оновлень для форм (лайки, коментарі, редагування).
- Налаштування prefetching та інтеграція з SSR/Next.js.
- Підключення DevTools для налагодження.
- Документація та навчання команди.
Процес роботи:
- Аналітика — аудит поточного стеку та API, визначення точок інтеграції.
- Проектування — схеми query keys, вибір стратегії кешування.
- Реалізація — написання хунків, тестування мутацій, оптимізація.
- Тестування — перевірка інвалідації, авторизації, edge-кейсів.
- Деплой — розгортання, моніторинг через DevTools.
Терміни: від 2 до 5 днів залежно від кількості Endpoints. Вартість стартує від $500 і до $1500 залежно від кількості ендпоінтів. Робота під ключ. Оцінимо проект безкоштовно — пишіть нам для консультації. Економія на подальшій підтримці окупає витрати протягом кількох тижнів.
Зв'яжіться з нами для консультації — ми допоможемо впровадити TanStack Query та скоротити час розробки на 30–50%. Отримайте готову архітектуру даних за лічені дні.







