Доки по разработке
This project is maintained by teniryte
useQueryimport { useQuery } from '@tanstack/react-query'
function Todos() {
const query = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 30_000,
})
if (query.isPending) return <Spinner />
if (query.isError) return <ErrorView error={query.error} />
return (
<>
<TodoList items={query.data} />
{query.isFetching ? <InlineSpinner /> : null}
</>
)
}
Главное различие:
isPending означает, что данных ещё нет.isFetching означает, что сетевой запрос идёт сейчас.isFetching.TanStack Query строится вокруг объекта опций. Одни и те же options можно использовать:
useQueryuseSuspenseQueryuseQueriesqueryClient.prefetchQueryqueryClient.ensureQueryDataПоэтому опции выгодно собирать в фабрики, а не разбрасывать по компонентам.
| Опция | Что делает |
|---|---|
queryKey |
Уникально описывает данные |
queryFn |
Загружает данные |
staleTime |
Как долго данные считаются свежими |
gcTime |
Когда удалить неактивные данные из кеша |
enabled |
Включать ли запрос |
retry / retryDelay |
Политика повторных попыток |
refetchOnMount |
Рефетчить ли при маунте stale-данных |
refetchOnWindowFocus |
Рефетчить ли при возврате во вкладку |
refetchOnReconnect |
Рефетчить ли при восстановлении сети |
refetchInterval |
Поллинг |
select |
Подписаться только на часть данных |
placeholderData |
Временные данные без записи в кеш |
initialData |
Начальные данные с записью в кеш |
meta |
Пользовательские метаданные |
staleTime и gcTimeЭто две самые важные настройки:
staleTime отвечает за свежесть.gcTime отвечает за удаление из памяти.Пример:
useQuery({
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId),
staleTime: 5 * 60_000,
gcTime: 30 * 60_000,
})
Что это значит:
Особые значения:
staleTime: Infinity означает “не рефетчить автоматически, пока явно не инвалидировали”.staleTime: 'static' означает “не рефетчить автоматически вообще, даже после инвалидции”.enabled и ленивые запросыconst query = useQuery({
queryKey: ['search', term],
queryFn: () => searchApi(term),
enabled: term.length > 2,
})
Когда enabled: false:
invalidateQueries и refetchQueriesrefetch() можно вызвать вручнуюЭто полезно для форм, фильтров и мастеров, где запрос должен стартовать только после ввода.
skipToken для type-safe отключенияimport { skipToken, useQuery } from '@tanstack/react-query'
const query = useQuery({
queryKey: ['search', term],
queryFn: term ? () => searchApi(term) : skipToken,
})
skipToken хорош, когда вы хотите сохранить строгую типизацию. Ограничение: refetch() с ним не работает.
placeholderData и initialDataconst postQuery = useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
placeholderData: (previousData) => previousData,
})
Разница:
placeholderData не попадает в кеш.initialData попадает в кеш.placeholderData запрос стартует как success, а isPlaceholderData === true.Когда что использовать:
placeholderData для мягкого перехода между ключами.initialData для данных из SSR, списка, роутера или заранее известного локального источника.selectconst query = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
select: (data) => data.filter((todo) => !todo.completed),
})
select полезен, когда:
Важно: select не должен бросать ошибки. Валидацию и падения делайте в queryFn.
useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
refetchInterval: (query) =>
query.state.data?.hasUnread ? 15_000 : false,
})
Поллинг не зависит от staleTime. Даже “свежие” данные будут обновляться по таймеру, если включён refetchInterval.
Хороший UX-паттерн:
!query.data && query.isPendingquery.data уже есть, при обновлении показывайте компактный индикатор по isFetchingЭто одна из причин, почему TanStack Query ощущается лучше, чем ручные useEffect + loading.
import { useIsFetching } from '@tanstack/react-query'
function GlobalBar() {
const isFetching = useIsFetching()
return isFetching ? <TopProgressBar /> : null
}
Можно сузить до семейства ключей:
const isProjectsFetching = useIsFetching({ queryKey: ['projects'] })
isPending там, где нужно isFetching.staleTime: 0 везде и удивляться постоянным рефетчам.enabled, а потом ожидать, что invalidateQueries обновит запрос.queryFn null при HTTP-ошибке вместо throw.