Dev Highlights

Доки по разработке

This project is maintained by teniryte

2. Запросы и Query Options

Базовый useQuery

import { 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}
    </>
  )
}

Главное различие:

Как мыслить про Query Options

TanStack Query строится вокруг объекта опций. Одни и те же options можно использовать:

Поэтому опции выгодно собирать в фабрики, а не разбрасывать по компонентам.

Самые важные опции

Опция Что делает
queryKey Уникально описывает данные
queryFn Загружает данные
staleTime Как долго данные считаются свежими
gcTime Когда удалить неактивные данные из кеша
enabled Включать ли запрос
retry / retryDelay Политика повторных попыток
refetchOnMount Рефетчить ли при маунте stale-данных
refetchOnWindowFocus Рефетчить ли при возврате во вкладку
refetchOnReconnect Рефетчить ли при восстановлении сети
refetchInterval Поллинг
select Подписаться только на часть данных
placeholderData Временные данные без записи в кеш
initialData Начальные данные с записью в кеш
meta Пользовательские метаданные

staleTime и gcTime

Это две самые важные настройки:

Пример:

useQuery({
  queryKey: ['profile', userId],
  queryFn: () => fetchProfile(userId),
  staleTime: 5 * 60_000,
  gcTime: 30 * 60_000,
})

Что это значит:

Особые значения:

enabled и ленивые запросы

const query = useQuery({
  queryKey: ['search', term],
  queryFn: () => searchApi(term),
  enabled: term.length > 2,
})

Когда enabled: false:

Это полезно для форм, фильтров и мастеров, где запрос должен стартовать только после ввода.

skipToken для type-safe отключения

import { skipToken, useQuery } from '@tanstack/react-query'

const query = useQuery({
  queryKey: ['search', term],
  queryFn: term ? () => searchApi(term) : skipToken,
})

skipToken хорош, когда вы хотите сохранить строгую типизацию. Ограничение: refetch() с ним не работает.

placeholderData и initialData

const postQuery = useQuery({
  queryKey: ['post', postId],
  queryFn: () => fetchPost(postId),
  placeholderData: (previousData) => previousData,
})

Разница:

Когда что использовать:

select

const 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-паттерн:

Это одна из причин, почему TanStack Query ощущается лучше, чем ручные useEffect + loading.

Глобальные индикаторы

import { useIsFetching } from '@tanstack/react-query'

function GlobalBar() {
  const isFetching = useIsFetching()
  return isFetching ? <TopProgressBar /> : null
}

Можно сузить до семейства ключей:

const isProjectsFetching = useIsFetching({ queryKey: ['projects'] })

Частые ошибки