The useSuspenseQuery hook is a specialized version of useQuery designed for React Suspense. It always suspends while fetching and throws errors to error boundaries, providing a more streamlined type signature.
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 >
Parameters
The query options object. A unique key for the query. Must be an array. queryKey : [ 'todos' ]
queryKey : [ 'todo' , todoId ]
queryFn
QueryFunction<TQueryFnData, TQueryKey>
required
The function that the query will use to request data. Cannot be skipToken. queryFn : async ({ queryKey }) => {
const response = await fetch ( `/api/todos/ ${ queryKey [ 1 ] } ` )
return response . json ()
}
staleTime
number | 'static' | ((query: Query) => number | 'static')
default: "0"
The time in milliseconds after data is considered stale.
The 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.
refetchIntervalInBackground
If true, refetch will continue while the tab is in the background.
refetchOnWindowFocus
boolean | 'always' | ((query: Query) => boolean | 'always')
default: "true"
Refetch on window focus behavior.
refetchOnReconnect
boolean | 'always' | ((query: Query) => boolean | 'always')
default: "true"
Refetch on reconnect behavior.
refetchOnMount
boolean | 'always' | ((query: Query) => boolean | 'always')
default: "true"
Refetch on mount behavior.
retry
boolean | number | ((failureCount: number, error: TError) => boolean)
default: "3"
Retry configuration for failed queries.
retryDelay
number | ((failureCount: number, error: TError) => number)
Delay between retries in milliseconds.
select
(data: TQueryData) => TData
Transform or select part of the data. select : data => data . items
networkMode
'online' | 'always' | 'offlineFirst'
default: "'online'"
Network mode configuration.
If false, the query will not retry on mount if it contains an error.
notifyOnChangeProps
Array<keyof UseSuspenseQueryResult> | 'all'
Properties to track for re-rendering.
Override the default QueryClient.
The following options are NOT available in useSuspenseQuery because they are automatically set:
enabled - Always true
suspense - Always true
throwOnError - Always throws errors to error boundaries
placeholderData - Not supported
Returns
UseSuspenseQueryResult<TData, TError>
The query result object. Data is always defined (never undefined). The last successfully resolved data for the query. Always defined (never undefined).
The error object for the query. Always null when data is available.
Always 'success' because the query suspends on pending and throws on error.
fetchStatus
'fetching' | 'paused' | 'idle'
The fetch status of the query.
Always false because the query suspends during pending state.
Always false because the query suspends during loading state.
true if the query is currently fetching (background refetch).
Always true when the component renders.
Always false because errors are thrown to error boundaries.
true if a background refetch is in progress.
true if the data is stale.
true if the query failed during a background refetch.
Timestamp of when the query most recently returned success.
Timestamp of when the query most recently returned an error.
The failure count for the query.
The failure reason for the query retry.
refetch
(options?: RefetchOptions) => Promise<UseSuspenseQueryResult>
Function to manually refetch the query.
Examples
Basic Usage
import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
function Todo ({ id } : { id : number }) {
const { data } = useSuspenseQuery ({
queryKey: [ 'todo' , id ],
queryFn : async () => {
const response = await fetch ( `/api/todos/ ${ id } ` )
return response . json ()
},
})
// data is always defined, no need to check for undefined
return < div >{data. title } </ div >
}
function App () {
return (
< Suspense fallback = {<div>Loading ...</ div > } >
< Todo id = { 1 } />
</ Suspense >
)
}
With Error Boundary
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { useSuspenseQuery } from '@tanstack/react-query'
function Todos () {
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 > } >
< Todos />
</ Suspense >
</ ErrorBoundary >
)
}
Multiple Suspense Queries
function TodoDetails ({ id } : { id : number }) {
const { data : todo } = useSuspenseQuery ({
queryKey: [ 'todo' , id ],
queryFn : () => fetchTodo ( id ),
})
const { data : comments } = useSuspenseQuery ({
queryKey: [ 'comments' , id ],
queryFn : () => fetchComments ( id ),
})
return (
< div >
< h1 >{todo. title } </ h1 >
< div >
{ comments . map ( comment => (
< p key = {comment. id } > {comment. text } </ p >
))}
</ div >
</ div >
)
}
function App () {
return (
< Suspense fallback = {<div>Loading ...</ div > } >
< TodoDetails id = { 1 } />
</ Suspense >
)
}
With Select
interface Todo {
id : number
title : string
completed : boolean
}
function CompletedTodos () {
const { data : completedTodos } = useSuspenseQuery ({
queryKey: [ 'todos' ],
queryFn: fetchTodos ,
select : ( todos : Todo []) => todos . filter ( todo => todo . completed ),
})
return (
< ul >
{ completedTodos . map ( todo => (
< li key = {todo. id } > {todo. title } </ li >
))}
</ ul >
)
}
Nested Suspense Boundaries
function UserProfile ({ userId } : { userId : number }) {
const { data : user } = useSuspenseQuery ({
queryKey: [ 'user' , userId ],
queryFn : () => fetchUser ( userId ),
})
return (
< div >
< h1 >{user. name } </ h1 >
< Suspense fallback = {<div>Loading posts ...</ div > } >
< UserPosts userId = { userId } />
</ Suspense >
</ div >
)
}
function UserPosts ({ userId } : { userId : number }) {
const { data : posts } = useSuspenseQuery ({
queryKey: [ 'posts' , userId ],
queryFn : () => fetchPosts ( userId ),
})
return (
< ul >
{ posts . map ( post => (
< li key = {post. id } > {post. title } </ li >
))}
</ ul >
)
}
function App () {
return (
< ErrorBoundary fallback = {<div>Error</div>} >
< Suspense fallback = {<div>Loading user ...</ div > } >
< UserProfile userId = { 1 } />
</ Suspense >
</ ErrorBoundary >
)
}
Notes
useSuspenseQuery requires React’s Suspense and Error Boundary to be set up in parent components.
The data property is always defined (never undefined) when the component renders, providing better type safety.
Errors are automatically thrown to the nearest error boundary, so you don’t need to handle error states manually.
The skipToken cannot be used with useSuspenseQuery. Use regular useQuery with enabled: false if you need conditional fetching.