Базовый пример работы с TanStack Query обычно строится вокруг загрузки данных с сервера и отображения состояний запроса: загрузки, ошибки и успешного ответа.
Установка библиотеки для React:
npm install @tanstack/react-query
Либо через Yarn:
yarn add @tanstack/react-query
После установки создаётся экземпляр QueryClient, который
отвечает за хранение кэша, управление запросами, повторные попытки,
обновления и синхронизацию состояния.
На верхнем уровне приложения подключается
QueryClientProvider.
import React fr om 'react';
import ReactDOM from 'react-dom/client';
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
QueryClient — центральный объект TanStack Query.
Он управляет:
QueryClientProvider передаёт экземпляр клиента через
React Context всем компонентам приложения.
Без него хуки TanStack Query работать не будут.
Теперь можно создать простой компонент с загрузкой данных.
import { useQuery } from '@tanstack/react-query';
async function fetchUsers() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
if (!response.ok) {
throw new Error('Ошибка загрузки');
}
return response.json();
}
export default function UsersList() {
const {
data,
isLoading,
isError,
error,
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) {
return <p>Загрузка...</p>;
}
if (isError) {
return <p>{error.message}</p>;
}
return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
);
}
Хук useQuery — основной инструмент для получения
серверных данных.
Он автоматически:
queryKey: ['users']
queryKey — уникальный ключ запроса.
Именно по нему TanStack Query хранит данные в кэше.
Ключ может быть:
['users']
Либо более сложным:
['users', userId]
Или:
['posts', {
page: 1,
lim it: 10,
}]
Ключ используется для:
Если два компонента используют одинаковый queryKey,
запрос выполняется один раз.
queryFn: fetchUsers
queryFn — функция загрузки данных.
Она должна:
Пример:
async function fetchUsers() {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Ошибка');
}
return response.json();
}
TanStack Query самостоятельно вызывает эту функцию и управляет её жизненным циклом.
if (isLoading) {
return <p>Загрузка...</p>;
}
Во время первого выполнения запроса isLoading будет
равен true.
if (isError) {
return <p>{error.message}</p>;
}
Если Promise завершился ошибкой, TanStack Query установит:
isError === true
А объект ошибки попадёт в error.
data.map(user => ...)
После успешного запроса данные становятся доступны через
data.
После первого запроса TanStack Query сохраняет данные в памяти.
При повторном открытии компонента:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
})
библиотека:
Это устраняет:
Предположим, существуют два компонента:
<UsersSidebar />
<UsersTable />
Оба используют:
queryKey: ['users']
Запрос выполнится только один раз.
Все компоненты получат единый источник данных.
Это одно из ключевых преимуществ TanStack Query перед ручным
использованием useEffect.
Классический React-код часто выглядит так:
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(response => response.json())
.then(data => {
setUsers(data);
})
.catch(error => {
setError(error);
})
.finally(() => {
setLoading(false);
});
}, []);
При таком подходе разработчик вручную управляет:
TanStack Query автоматизирует эти задачи.
После сворачивания вкладки и возврата обратно TanStack Query может автоматически обновить данные.
Это поведение включено по умолчанию.
Пример:
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
Если пользователь вернётся на вкладку браузера спустя некоторое время, библиотека выполнит повторный запрос в фоне.
По умолчанию данные считаются устаревшими сразу после загрузки.
Это можно изменить:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 1000 * 60,
});
Здесь данные считаются актуальными одну минуту.
В течение этого времени TanStack Query не будет повторно обращаться к серверу.
TanStack Query хранит данные в памяти даже после размонтирования компонентов.
Время хранения регулируется через gcTime.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 10,
},
},
});
В данном случае кэш хранится 10 минут.
При ошибках TanStack Query автоматически повторяет запрос.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 3,
});
Библиотека выполнит до трёх повторных попыток.
Отключение повторов:
retry: false
TanStack Query умеет обновлять данные без полной перезагрузки интерфейса.
Во время фонового обновления:
Для отслеживания используется:
isFetching
Пример:
const {
data,
isFetching,
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
{isFetching && <p>Обновление...</p>}
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query';
const queryClient = new QueryClient();
async function fetchPosts() {
const response = await fetch(
'https://jsonplaceholder.typicode.com/posts'
);
if (!response.ok) {
throw new Error('Ошибка сервера');
}
return response.json();
}
function Posts() {
const {
data,
isLoading,
isError,
error,
isFetching,
} = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 1000 * 30,
});
if (isLoading) {
return <h2>Загрузка...</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
return (
<div>
{isFetching && (
<p>Обновление данных...</p>
)}
{data.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</article>
))}
</div>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Posts />
</QueryClientProvider>
);
}
Без TanStack Query приходится вручную создавать:
loading;error;data;useEffect;С библиотекой большая часть логики уже реализована.
TanStack Query создаёт централизованное хранилище серверных данных.
Это устраняет:
Благодаря кэшированию уменьшается количество HTTP-запросов.
Особенно заметно это в:
Во многих случаях исчезает необходимость хранить серверные данные в:
TanStack Query берёт на себя именно серверное состояние, оставляя глобальным хранилищам только клиентские данные интерфейса.
При первом рендере:
useQuery(...)
происходит:
queryFn, если данных нет;isLoading;При повторном использовании:
Хук возвращает большое количество свойств.
Наиболее используемые:
const {
data,
error,
isLoading,
isError,
isSuccess,
isFetching,
refetch,
status,
} = useQuery(...);
Содержит результат запроса.
Содержит объект ошибки.
Первичная загрузка.
Любой активный запрос, включая фоновые обновления.
Успешное выполнение запроса.
Ручной повторный запрос.
Пример:
<button onCl ick={() => refetch()}>
Обновить
</button>
const {
data,
refetch,
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
<button onCl ick={refetch}>
Перезагрузить
</button>
TanStack Query выполнит новый запрос и обновит кэш.
Для отладки существует отдельный пакет:
npm install @tanstack/react-query-devtools
Подключение:
import { ReactQueryDevtools }
from '@tanstack/react-query-devtools';
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
Devtools позволяют:
Компонент
↓
useQuery
↓
queryKey
↓
Проверка кэша
↓
queryFn
↓
HTTP-запрос
↓
Кэширование
↓
Обновление интерфейса
Ошибка:
No QueryClient set
Причина:
useQuery(...)
используется вне QueryClientProvider.
Плохо:
queryKey: [Math.random()]
Кэширование перестанет работать корректно.
Плохо:
async function fetchUsers() {
setState(...)
return fetch(...)
}
queryFn должна заниматься только загрузкой данных.
useQuery предназначен только для получения данных.
Для:
используется useMutation.
Часто запросы выносятся в отдельные API-модули.
export async function fetchUsers() {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Ошибка загрузки');
}
return response.json();
}
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../api/users';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
}
import { useUsers } from './hooks/useUsers';
export default function UsersPage() {
const { data } = useUsers();
return (
<div>
{data?.map(user => (
<p key={user.id}>
{user.name}
</p>
))}
</div>
);
}
Такой подход делает архитектуру: