Любой запрос в TanStack Query проходит через набор состояний, отражающих текущий этап работы с данными. Эти состояния позволяют управлять интерфейсом, показывать индикаторы загрузки, сообщения об ошибках и готовые данные без ручного отслеживания множества флагов.
Основными состояниями считаются:
loading — запрос выполняется;error — запрос завершился ошибкой;success — данные успешно получены.В TanStack Query эти состояния представлены через набор свойств,
которые возвращает хук useQuery.
Пример стандартного запроса:
import { useQuery } from '@tanstack/react-query'
function Users() {
const query = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка загрузки')
}
return response.json()
}
})
return null
}
Объект query содержит множество свойств:
{
data,
error,
isLoading,
isError,
isSuccess,
status,
fetchStatus,
refetch,
...
}
Наиболее важными являются:
| Свойство | Назначение |
|---|---|
isLoading |
Идёт первая загрузка |
isError |
Запрос завершился ошибкой |
isSuccess |
Данные успешно получены |
data |
Полученные данные |
error |
Объект ошибки |
status |
Текстовое состояние |
Состояние loading означает, что запрос ещё не завершён и
данные пока отсутствуют.
В этот момент обычно отображаются:
function Users() {
const {
data,
isLoading
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (isLoading) {
return <div>Загрузка...</div>
}
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Когда компонент монтируется:
TanStack Query проверяет наличие данных в кеше.
Если данных нет:
isLoading === true;status === 'loading'.После завершения:
success или
error.Это одна из самых важных тем в TanStack Query.
Показывает первую загрузку, когда данных ещё нет.
isLoading === true
означает:
Показывает любой сетевой запрос, включая фоновые обновления.
isFetching === true
может означать:
const {
data,
isLoading,
isFetching
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
isLoading = true
isFetching = true
data = undefined
isLoading = false
isFetching = false
data = [...]
isLoading = false
isFetching = true
data = [...]
Интерфейс уже имеет данные, но запрос обновляется.
function Users() {
const {
data,
isLoading,
isFetching
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
refetchOnWindowFocus: true
})
if (isLoading) {
return <div>Первичная загрузка...</div>
}
return (
<>
{isFetching && (
<div>Обновление данных...</div>
)}
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
</>
)
}
Состояние error возникает, если запрос завершился
неудачно.
Причинами могут быть:
queryFn.TanStack Query считает запрос ошибочным только при выбрасывании исключения.
Неправильный пример:
queryFn: async () => {
const response = await fetch('/api/users')
return response.json()
}
fetch не выбрасывает ошибку при HTTP 404 или 500.
queryFn: async () => {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка сервера')
}
return response.json()
}
function Users() {
const {
data,
isError,
error
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (isError) {
return (
<div>
Ошибка: {error.message}
</div>
)
}
return (
<div>{JSON.stringify(data)}</div>
)
}
Свойство error содержит объект исключения.
Обычно это экземпляр Error.
console.log(error)
Пример:
Error: Ошибка сервера
В TypeScript ошибка обычно имеет тип:
unknown
Поэтому часто используется приведение:
if (error instanceof Error) {
console.log(error.message)
}
По умолчанию TanStack Query автоматически повторяет запрос при ошибке.
Стандартное значение:
retry: 3
Это означает:
error.useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: false
})
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 5
})
Интервал между попытками:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retryDelay: 1000
})
Состояние success означает:
data содержит результат.function Users() {
const {
data,
isSuccess
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (isSuccess) {
return (
<div>
Пользователей: {data.length}
</div>
)
}
return null
}
После успешной загрузки:
TanStack Query предоставляет строковое состояние:
status
Возможные значения:
| Значение | Описание |
|---|---|
loading |
Выполняется загрузка |
error |
Произошла ошибка |
success |
Данные получены |
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (query.status === 'loading') {
return <div>Загрузка...</div>
}
if (query.status === 'error') {
return <div>Ошибка</div>
}
return <div>Готово</div>
Эти варианты эквивалентны:
query.status === 'loading'
и
query.isLoading
Однако boolean-флаги обычно удобнее.
Часто используется полный набор проверок:
function Users() {
const {
data,
error,
isLoading,
isError
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
if (isLoading) {
return <div>Загрузка...</div>
}
if (isError) {
return (
<div>
{error.message}
</div>
)
}
return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
Такой подход называется early return.
Он делает код:
Иногда загрузку нужно скрыть.
Для этого можно использовать временные данные:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
placeholderData: []
})
Теперь:
isLoading === false
поскольку данные уже существуют.
initialData также влияет на состояние.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
initialData: []
})
Запрос сразу считается успешным:
status === 'success'
Даже если реальный запрос ещё выполняется.
Помимо status существует fetchStatus.
Возможные значения:
| Значение | Описание |
|---|---|
fetching |
Выполняется запрос |
paused |
Запрос приостановлен |
idle |
Запрос не выполняется |
Отвечает за состояние данных.
Отвечает за состояние сетевой активности.
{
status: 'success',
fetchStatus: 'fetching'
}
Это означает:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
enabled: false
})
Состояние:
status = 'loading'
fetchStatus = 'idle'
Запрос ещё не стартовал.
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
query.refetch()
Во время повторного запроса:
isFetching === true
Но:
isLoading === false
Потому что данные уже существуют.
Если данные свежие:
staleTime: 1000 * 60
TanStack Query не будет повторно загружать их при повторном открытии компонента.
Следовательно:
isLoading === false
Полное скрытие интерфейса при каждом refetch.
if (isLoading || isFetching) {
return <Spinner />
}
Интерфейс постоянно мигает.
Разделение первичной загрузки и фонового обновления.
if (isLoading) {
return <FullPageLoader />
}
И отдельно:
{isFetching && <SmallLoader />}
После success данные могут быть пустыми.
if (data.length === 0) {
return <div>Список пуст</div>
}
Это отдельное состояние интерфейса, не связанное с
error.
Неправильно:
if (isFetching) {
return <Spinner />
}
Интерфейс будет исчезать при каждом обновлении.
Неправильно:
if (!response.ok) {
return null
}
TanStack Query не узнает об ошибке.
const { data } = useQuery(...)
При ошибке компонент может сломаться.
function Component() {
const {
data,
error,
isLoading,
isError,
isFetching
} = useQuery({
queryKey: ['resource'],
queryFn: fetchResource
})
if (isLoading) {
return <Loader />
}
if (isError) {
return (
<ErrorMessage>
{error.message}
</ErrorMessage>
)
}
return (
<>
{isFetching && (
<UpdatingIndicator />
)}
<Content data={data} />
</>
)
}
Типичный сценарий:
status = 'loading'
fetchStatus = 'fetching'
status = 'success'
fetchStatus = 'idle'
status = 'success'
fetchStatus = 'fetching'
Если данные уже были:
status = 'success'
может сохраниться, несмотря на ошибку refetch.
Это важная особенность TanStack Query: библиотека старается не терять рабочие данные.
При ошибке фонового обновления:
Это значительно улучшает UX по сравнению с традиционными подходами к загрузке данных.