Capítulo 41 de 80

Chapter 41: Suspense

Core Idea

useSuspenseQuery/useSuspenseInfiniteQuery/useSuspenseQueries replace status/error handling with <Suspense> fallbacks and error boundaries, guaranteeing data is always defined at the type level — at the cost of losing per-query enabled toggling and placeholderData.

Key Concepts

  • The trade: with the suspense hooks, data is guaranteed non-undefined (loading/error states are handled entirely by <Suspense>/error boundaries), but queries can no longer be conditionally enabled/disabled per-instance, and placeholderData has no suspense equivalent — wrap query-key-changing updates in startTransition instead, to avoid the fallback flashing during a key change.
  • Default throwOnError isn't "throw everything": only throws to the nearest error boundary when there's no data to show at all (typeof query.state.data === 'undefined') — a query that once succeeded keeps rendering its (possibly stale) data through a later error rather than unmounting into a boundary. This default can't be overridden (it would let data become undefined), but you can manually throw error from the component when error && !isFetching if you want stricter behavior.
  • Mutation errors: pass throwOnError: true to a useMutation if its errors should also propagate to the nearest error boundary, like suspense queries do by default.
  • Resetting error boundaries: QueryErrorResetBoundary (component) or useQueryErrorResetBoundary (hook) resets a query's error state so the boundary's "try again" can actually re-attempt the query rather than immediately re-throwing the same cached error. The hook resets errors within the nearest QueryErrorResetBoundary, or globally if none is present.
  • Fetch-on-render vs. render-as-you-fetch: suspense mode works as fetch-on-render out of the box (mounting triggers the fetch and suspends) with zero extra config; upgrading to render-as-you-fetch means layering prefetching (on route transitions, hover, etc.) so data starts loading before the component even mounts.
  • Streaming on the server (Next.js, experimental): @tanstack/react-query-next-experimental's ReactQueryStreamedHydration lets useSuspenseQuery fetch server-side in a Client Component with results streamed to the client as Suspense boundaries resolve — no manual prefetch/dehydrate wiring needed, at the cost described in Advanced Server Rendering (client-navigation waterfalls aren't flattened the same way).

Code Examples

const { data } = useSuspenseQuery({ queryKey, queryFn }) // data is never undefined

// Manually forcing all errors to the boundary, since throwOnError can't be overridden
const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })
if (error && !isFetching) throw error

// Resetting a boundary so "Try again" actually retries
<QueryErrorResetBoundary>
  {({ reset }) => (
    <ErrorBoundary onReset={reset} fallbackRender={({ resetErrorBoundary }) => (
      <button onClick={resetErrorBoundary}>Try again</button>
    )}>
      <Page />
    </ErrorBoundary>
  )}
</QueryErrorResetBoundary>
  • What it demonstrates: data's guaranteed-defined shape under Suspense, manually escalating a stale-but-erroring query to the boundary, and wiring a resettable error boundary so retries work.

Key Takeaways

  1. Pair every suspense-mode error boundary with QueryErrorResetBoundary/useQueryErrorResetBoundary — without it, "Try again" re-renders into the same cached error instead of retrying the query.
  2. Don't reach for enabled/placeholderData under suspense hooks — they don't apply there; restructure with conditional rendering or startTransition instead.
  3. Multiple useSuspenseQuery calls in one component run serially, not in parallel — use useSuspenseQueries (see Performance & Request Waterfalls) whenever more than one lives together.

Connects To

  • Performance & Request Waterfalls: the serial-suspense-queries pitfall and its useSuspenseQueries fix.
  • Advanced Server Rendering: streaming pending queries into useSuspenseQuery on the server.
  • useSuspenseQuery / useSuspenseInfiniteQuery / useSuspenseQueries: the full API reference for each hook.