Перейти к содержимому

8. Query: Suspense режим

Традиционный подход с useQuery требует явной обработки каждого состояния:

function UserList() {
const { data, isLoading, isError, error } = useQuery(...)
if (isLoading) return <Spinner />
if (isError) return <Error message={error.message} />
return <List data={data} />
}

Suspense подход разделяет ответственность: компонент всегда получает данные, а загрузка и ошибки обрабатываются выше в дереве компонентов:

// Компонент получает данные напрямую — нет isLoading, нет isError
function UserList() {
const { data } = useSuspenseQuery(...)
return <List data={data} /> // data всегда определена
}
// Родитель обрабатывает состояния
function App() {
return (
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Spinner />}>
<UserList />
</Suspense>
</ErrorBoundary>
)
}

Идентичен useQuery, но с ключевым отличием: гарантирует, что data всегда определена. Если данных нет — компонент “suspends” (приостанавливается), и React показывает fallback из <Suspense>.

import { useSuspenseQuery } from '@tanstack/react-query'
function UserProfile({ userId }: { userId: number }) {
// data гарантированно User, не User | undefined
const { data } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
})
// Никакого isLoading/isError — React Suspense обрабатывает это
return <div>{data.name}</div>
}

Для перехвата ошибок в Suspense-режиме используется ErrorBoundary:

import { ErrorBoundary } from 'react-error-boundary'
function App() {
return (
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>Ошибка: {error.message}</p>
<button onClick={resetErrorBoundary}>Повторить</button>
</div>
)}
>
<Suspense fallback={<Spinner />}>
<UserList />
</Suspense>
</ErrorBoundary>
)
}
// Параллельные запросы с Suspense
const [{ data: users }, { data: posts }] = useSuspenseQueries({
queries: [
{ queryKey: ['users'], queryFn: getUsers },
{ queryKey: ['posts'], queryFn: getPosts },
],
})
// Бесконечный скролл с Suspense
const { data } = useSuspenseInfiniteQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
initialPageParam: 0,
getNextPageParam: (last) => last.nextPage,
})

Используйте Suspense, когда:

  • Хотите чистые компоненты без if/else для загрузки
  • Используете параллельные запросы
  • Работаете с React Server Components
  • Хотите “waterfall-free” загрузку

Оставайтесь с useQuery, когда:

  • Нужен тонкий контроль над состояниями загрузки
  • Partial loading (показываем данные по частям)
  • Компонент должен показывать что-то даже во время фоновой загрузки