Skip to main content

useSuspenseQuery

The useSuspenseQuery hook is designed to work with React Suspense. It suspends rendering while data is being fetched and always returns defined data (never undefined).

Import

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

Signature

function useSuspenseQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  options: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  queryClient?: QueryClient,
): UseSuspenseQueryResult<TData, TError>

Type Parameters

TQueryFnData
type
default:"unknown"
The type of data returned by the query function
TError
type
default:"DefaultError"
The type of error that can be thrown by the query function
TData
type
default:"TQueryFnData"
The type of data returned by the select function (if provided), otherwise same as TQueryFnData
TQueryKey
type
default:"QueryKey"
The type of the query key

Parameters

options
UseSuspenseQueryOptions
required
Configuration options for the suspense query. Similar to UseQueryOptions but with some differences:
queryKey
TQueryKey
required
A unique key for the query. Must be an array.
queryFn
QueryFunction<TQueryFnData, TQueryKey>
required
The function that will be called to fetch data. Note: skipToken is not allowed with useSuspenseQuery.
staleTime
number | ((query: Query) => number)
default:"0"
Time in milliseconds after data is considered stale
gcTime
number
default:"300000"
Time in milliseconds that unused/inactive cache data remains in memory
refetchInterval
number | false | ((query: Query) => number | false)
default:"false"
If set to a number, the query will continuously refetch at this frequency in milliseconds
refetchIntervalInBackground
boolean
default:"false"
If set to true, the query will continue to refetch while the window is in the background
refetchOnWindowFocus
boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
If set to true, the query will refetch on window focus if the data is stale
refetchOnMount
boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
If set to true, the query will refetch on mount if the data is stale
refetchOnReconnect
boolean | 'always' | ((query: Query) => boolean | 'always')
default:"true"
If set to true, the query will refetch on reconnect if the data is stale
retry
boolean | number | (failureCount: number, error: TError) => boolean
default:"3"
Number of retry attempts or function to determine if a request should be retried
retryDelay
number | (retryAttempt: number, error: TError) => number
Function that receives a retry attempt number and returns the delay to apply before the next attempt
select
(data: TQueryFnData) => TData
Function to transform or select a part of the data returned by the query function
structuralSharing
boolean | (oldData: unknown, newData: unknown) => unknown
default:"true"
Set this to false to disable structural sharing between query results
networkMode
'online' | 'always' | 'offlineFirst'
default:"'online'"
Controls when the query function is allowed to execute
notifyOnChangeProps
Array<keyof UseSuspenseQueryResult> | 'all'
If set, the component will only re-render when one of the listed properties change
meta
Record<string, unknown>
Optional metadata that can be used by query client plugins
queryClient
QueryClient
Optional QueryClient instance to use. If not provided, the client from the nearest QueryClientProvider will be used.

Returns

The result is similar to useQuery but with guaranteed data:
data
TData
The data returned by the query function. Always defined (never undefined) once the component renders.
error
TError | null
The error object for the query, if an error occurred
status
'pending' | 'error' | 'success'
The status of the query. Will be success when the component renders (after suspense resolves).
fetchStatus
'fetching' | 'paused' | 'idle'
The fetch status of the query
isSuccess
boolean
Will be true when the component renders (after suspense resolves)
isError
boolean
Derived from status. Will be true if the query is in error status
isFetching
boolean
Will be true whenever the query is fetching (including background refetching)
isRefetching
boolean
Will be true whenever the query is refetching (fetching while data already exists)
isStale
boolean
Will be true if the data in the cache is invalidated or if the data is older than the given staleTime
refetch
(options?: { throwOnError?: boolean, cancelRefetch?: boolean }) => Promise<UseSuspenseQueryResult>
Function to manually refetch the query
dataUpdatedAt
number
Timestamp of when the data was last updated
errorUpdatedAt
number
Timestamp of when the error was last updated
failureCount
number
The failure count for the query
failureReason
TError | null
The failure reason for the query retry

Differences from useQuery

  1. Suspends rendering: The component will suspend (show fallback) while data is being fetched initially
  2. Data always defined: The data property is always defined (never undefined) when the component renders
  3. No enabled option: Queries always run (cannot be disabled)
  4. No placeholderData: Placeholder data is not supported
  5. No throwOnError option: Errors are always thrown to the nearest Error Boundary
  6. No skipToken: Cannot conditionally skip the query
  7. No isPending state: The component won’t render until data is available
  8. No isPlaceholderData: Not included in result type

Examples

Basic Usage

import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'

function TodoList() {
  // data is always defined - no need to check for undefined!
  const { data } = useSuspenseQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const response = await fetch('/api/todos')
      return response.json()
    },
  })

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <TodoList />
    </Suspense>
  )
}

With Type Safety

import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'

interface Todo {
  id: number
  title: string
  completed: boolean
}

function TodoList() {
  const { data } = useSuspenseQuery<Todo[]>({
    queryKey: ['todos'],
    queryFn: async () => {
      const response = await fetch('/api/todos')
      return response.json()
    },
  })

  // data is typed as Todo[] (not Todo[] | undefined)
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading todos...</div>}>
      <TodoList />
    </Suspense>
  )
}

With Error Boundary

import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { useSuspenseQuery } from '@tanstack/react-query'

function TodoList() {
  const { data } = useSuspenseQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  })

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

function App() {
  return (
    <ErrorBoundary fallback={<div>Error loading todos</div>}>
      <Suspense fallback={<div>Loading...</div>}>
        <TodoList />
      </Suspense>
    </ErrorBoundary>
  )
}

Multiple Suspense Queries

import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'

function UserProfile({ userId }: { userId: number }) {
  const { data: user } = useSuspenseQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  })

  const { data: posts } = useSuspenseQuery({
    queryKey: ['posts', userId],
    queryFn: () => fetchUserPosts(userId),
  })

  return (
    <div>
      <h1>{user.name}</h1>
      <div>
        {posts.map((post) => (
          <article key={post.id}>{post.title}</article>
        ))}
      </div>
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading profile...</div>}>
      <UserProfile userId={1} />
    </Suspense>
  )
}

With Data Transformation

import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'

function IncompleteTodos() {
  const { data } = useSuspenseQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select: (data) => data.filter((todo) => !todo.completed),
  })

  // data contains only incomplete todos, and is always defined
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <IncompleteTodos />
    </Suspense>
  )
}

Nested Suspense Boundaries

import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'

function User() {
  const { data } = useSuspenseQuery({
    queryKey: ['user'],
    queryFn: fetchUser,
  })

  return <div>{data.name}</div>
}

function Posts() {
  const { data } = useSuspenseQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  })

  return (
    <div>
      {data.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading user...</div>}>
        <User />
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <Posts />
      </Suspense>
    </div>
  )
}

Best Practices

  1. Always wrap with Suspense: Components using useSuspenseQuery must be wrapped with a Suspense boundary
  2. Use Error Boundaries: Wrap with an Error Boundary to handle errors gracefully
  3. Consider granular boundaries: Use multiple Suspense boundaries for better UX when loading different parts of the UI
  4. Prefetch data: Consider prefetching data before rendering to avoid showing loading states
  5. Not for conditional queries: Use useQuery with enabled if you need conditional fetching

Source

Implementation: useSuspenseQuery.ts:8

Build docs developers (and LLMs) love