Suspense-подход в TanStack Query строится вокруг идеи переноса
состояния загрузки на уровень React Suspense, исключая необходимость
ручной обработки isLoading, isFetching в
компонентах. Данные становятся частью механизма «приостановки» рендера,
а не состоянием, управляемым внутри UI-логики.
В TanStack Query Suspense интегрируется через специальные режимы запроса и обёртки React Suspense + Error Boundary.
React Suspense перехватывает «незавершённый» процесс получения данных и откладывает рендер компонента до момента, когда данные станут доступны.
В контексте TanStack Query это реализуется через:
TanStack Query предоставляет два основных способа работы с Suspense:
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true
}
}
})
В этом режиме каждый query автоматически переходит в Suspense-модель.
import { useQuery } from '@tanstack/react-query'
const fetchUser = async () => {
const res = await fetch('/api/user')
return res.json()
}
function User() {
const { data } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
suspense: true
})
return <div>{data.name}</div>
}
Suspense активируется только для конкретного запроса.
В новых версиях TanStack Query используется отдельный хук
useSuspenseQuery, который убирает необходимость ручного
указания suspense: true.
import { useSuspenseQuery } from '@tanstack/react-query'
function User() {
const { data } = useSuspenseQuery({
queryKey: ['user'],
queryFn: async () => {
const res = await fetch('/api/user')
return res.json()
}
})
return <div>{data.name}</div>
}
Поведение:
isLoading больше не требуетсяSuspense-режим невозможен без React Suspense boundary.
import { Suspense } from 'react'
function App() {
return (
<Suspense fallback={<div>Загрузка данных...</div>}>
<User />
</Suspense>
)
}
Fallback отображается до завершения всех зависимых запросов внутри дерева компонентов.
Suspense не обрабатывает ошибки. Для этого используется Error Boundary.
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error }) {
return <div>Ошибка: {error.message}</div>
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Suspense fallback={<div>Загрузка...</div>}>
<User />
</Suspense>
</ErrorBoundary>
)
}
TanStack Query использует кеш как основной источник синхронизации Suspense.
Ключевые особенности:
staleTime определяет, считается ли кеш актуальным.
useSuspenseQuery({
queryKey: ['user'],
queryFn: fetchUser,
staleTime: 1000 * 60
})
Поведение:
Suspense при этом не блокирует отображение, если есть кеш.
Prefetch позволяет подготовить данные до входа в компонент.
queryClient.prefetchQuery({
queryKey: ['user'],
queryFn: fetchUser
})
Эффект:
Несколько Suspense-запросов внутри одного дерева объединяются React-ом.
function Dashboard() {
const user = useSuspenseQuery({ queryKey: ['user'], queryFn: fetchUser })
const posts = useSuspenseQuery({ queryKey: ['posts'], queryFn: fetchPosts })
return (
<>
<div>{user.data.name}</div>
<div>{posts.data.length}</div>
</>
)
}
Поведение:
Suspense в TanStack Query совместим с streaming SSR сценариями.
Особенности:
Hydration позволяет избежать повторных запросов на клиенте.
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['user'],
queryFn: fetchUser
})
return {
props: {
dehydratedState: dehydrate(queryClient)
}
}
}
Клиент:
function App({ dehydratedState }) {
return (
<HydrationBoundary state={dehydratedState}>
<Suspense fallback={<div>Loading...</div>}>
<User />
</Suspense>
</HydrationBoundary>
)
}
Поведение ошибок отличается от классического режима:
retryuseSuspenseQuery({
queryKey: ['user'],
queryFn: fetchUser,
retry: 2
})
isLoadingisErrorisFetchingSuspense позволяет переносить загрузку данных на уровень маршрутов и компонентов.
const User = React.lazy(() => import('./User'))
function App() {
return (
<Suspense fallback={<div>Loading route...</div>}>
<User />
</Suspense>
)
}
В сочетании с TanStack Query данные загружаются синхронно с компонентом.
В Suspense-режиме фоновые обновления продолжают работать.
useSuspenseQuery({
queryKey: ['user'],
queryFn: fetchUser,
refetchOnWindowFocus: true
})
Поведение:
Suspense-запросы можно комбинировать в отдельные слои данных.
Каждый слой может иметь собственный boundary, что позволяет изолировать задержки загрузки.
Один boundary на всё приложение приводит к централизованной загрузке.
Каждый модуль управляет собственным состоянием загрузки.
Комбинация глобального fallback и локальных fallback-узлов для частичных загрузок.
Ключевые механизмы оптимизации:
При одновременных запросах с одинаковым queryKey:
Suspense не блокирует последующие обновления:
Модель превращается в поток данных, а не статический запрос.