React Query can also be used with React's Suspense for Data Fetching API's. For this, we have dedicated hooks:
When using suspense mode, status
states and error
objects are not needed and are then replaced by usage of the React.Suspense
component (including the use of the fallback
prop and React error boundaries for catching errors). Please read the Resetting Error Boundaries and look at the Suspense Example for more information on how to set up suspense mode.
If you want mutations to propagate errors to the nearest error boundary (similar to queries), you can set the throwOnError
option to true
as well.
Enabling suspense mode for a query:
1import { useSuspenseQuery } from '@tanstack/react-query'
2
3const { data } = useSuspenseQuery({ queryKey, queryFn })
This works nicely in TypeScript, because data
is guaranteed to be defined (as errors and loading states are handled by Suspense- and ErrorBoundaries).
On the flip side, you therefore can't conditionally enable / disable the Query. This generally shouldn't be necessary for dependent Queries because with suspense, all your Queries inside one component are fetched in serial.
placeholderData
also doesn't exist for this Query. To prevent the UI from being replaced by a fallback during an update, wrap your updates that change the QueryKey into startTransition.
throwOnError default #
Not all errors are thrown to the nearest Error Boundary per default - we're only throwing errors if there is no other data to show. That means if a Query ever successfully got data in the cache, the component will render, even if data is stale
. Thus, the default for throwOnError
is:
throwOnError: (error, query) => typeof query.state.data === 'undefined'
Since you can't change throwOnError
(because it would allow for data
to become potentially undefined
), you have to throw errors manually if you want all errors to be handled by Error Boundaries:
1import { useSuspenseQuery } from '@tanstack/react-query'
2
3const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })
4
5if (error && !isFetching) {
6 throw error
7}
8
9// continue rendering data
Resetting Error Boundaries #
Whether you are using suspense or throwOnError in your queries, you will need a way to let queries know that you want to try again when re-rendering after some error occurred.
Query errors can be reset with the QueryErrorResetBoundary
component or with the useQueryErrorResetBoundary
hook.
When using the component it will reset any query errors within the boundaries of the component:
1import { QueryErrorResetBoundary } from '@tanstack/react-query'
2import { ErrorBoundary } from 'react-error-boundary'
3
4const App = () => (
5 <QueryErrorResetBoundary>
6 {({ reset }) => (
7 <ErrorBoundary
8 onReset={reset}
9 fallbackRender={({ resetErrorBoundary }) => (
10 <div>
11 There was an error!
12 <Button onClick={() => resetErrorBoundary()}>Try again</Button>
13 </div>
14 )}
15 >
16 <Page />
17 </ErrorBoundary>
18 )}
19 </QueryErrorResetBoundary>
20)
When using the hook it will reset any query errors within the closest QueryErrorResetBoundary
. If there is no boundary defined it will reset them globally:
1import { useQueryErrorResetBoundary } from '@tanstack/react-query'
2import { ErrorBoundary } from 'react-error-boundary'
3
4const App = () => {
5 const { reset } = useQueryErrorResetBoundary()
6 return (
7 <ErrorBoundary
8 onReset={reset}
9 fallbackRender={({ resetErrorBoundary }) => (
10 <div>
11 There was an error!
12 <Button onClick={() => resetErrorBoundary()}>Try again</Button>
13 </div>
14 )}
15 >
16 <Page />
17 </ErrorBoundary>
18 )
19}
Fetch-on-render vs Render-as-you-fetch #
Out of the box, React Query in suspense
mode works really well as a Fetch-on-render solution with no additional configuration. This means that when your components attempt to mount, they will trigger query fetching and suspend, but only once you have imported them and mounted them. If you want to take it to the next level and implement a Render-as-you-fetch model, we recommend implementing Prefetching on routing callbacks and/or user interactions events to start loading queries before they are mounted and hopefully even before you start importing or mounting their parent components.
Suspense on the Server with streaming #
If you are using NextJs
, you can use our experimental integration for Suspense on the Server: @tanstack/react-query-next-experimental
. This package will allow you to fetch data on the server (in a client component) by just calling useSuspenseQuery
in your component. Results will then be streamed from the server to the client as SuspenseBoundaries resolve.
To achieve this, wrap your app in the ReactQueryStreamedHydration
component:
1// app/providers.tsx
2'use client'
3
4import {
5 isServer,
6 QueryClient,
7 QueryClientProvider,
8} from '@tanstack/react-query'
9import * as React from 'react'
10import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'
11
12function makeQueryClient() {
13 return new QueryClient({
14 defaultOptions: {
15 queries: {
16 // With SSR, we usually want to set some default staleTime
17 // above 0 to avoid refetching immediately on the client
18 staleTime: 60 * 1000,
19 },
20 },
21 })
22}
23
24let browserQueryClient: QueryClient | undefined = undefined
25
26function getQueryClient() {
27 if (isServer) {
28 // Server: always make a new query client
29 return makeQueryClient()
30 } else {
31 // Browser: make a new query client if we don't already have one
32 // This is very important, so we don't re-make a new client if React
33 // suspends during the initial render. This may not be needed if we
34 // have a suspense boundary BELOW the creation of the query client
35 if (!browserQueryClient) browserQueryClient = makeQueryClient()
36 return browserQueryClient
37 }
38}
39
40export function Providers(props: { children: React.ReactNode }) {
41 // NOTE: Avoid useState when initializing the query client if you don't
42 // have a suspense boundary between this and the code that may
43 // suspend because React will throw away the client on the initial
44 // render if it suspends and there is no boundary
45 const queryClient = getQueryClient()
46
47 return (
48 <QueryClientProvider client={queryClient}>
49 <ReactQueryStreamedHydration>
50 {props.children}
51 </ReactQueryStreamedHydration>
52 </QueryClientProvider>
53 )
54}
For more information, check out the NextJs Suspense Streaming Example and the Advanced Rendering & Hydration guide.