Framework
Version

Suspense

React Query kann auch mit den Data Fetching APIs von Reacts Suspense verwendet werden. Hierfür haben wir spezielle Hooks

Bei Verwendung des Suspense-Modus sind die Status-Zustände status und die Fehlerobjekte error nicht erforderlich und werden dann durch die Verwendung der React.Suspense Komponente ersetzt (einschließlich der Verwendung der fallback Prop und von React Fehlergrenzen zum Abfangen von Fehlern). Bitte lesen Sie die Fehlergrenzen zurücksetzen und sehen Sie sich das Suspense-Beispiel für weitere Informationen zur Einrichtung des Suspense-Modus an.

Wenn Sie möchten, dass Mutationen Fehler an die nächste Fehlergrenze weitergeben (ähnlich wie bei Abfragen), können Sie die Option throwOnError ebenfalls auf true setzen.

Aktivieren des Suspense-Modus für eine Abfrage

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

const { data } = useSuspenseQuery({ queryKey, queryFn })
import { useSuspenseQuery } from '@tanstack/react-query'

const { data } = useSuspenseQuery({ queryKey, queryFn })

Dies funktioniert gut in TypeScript, da data garantiert definiert ist (da Fehler und Ladezustände durch Suspense- und Fehlergrenzen gehandhabt werden).

Auf der anderen Seite können Sie die Abfrage daher nicht bedingt aktivieren / deaktivieren. Dies sollte im Allgemeinen für abhängige Abfragen nicht notwendig sein, da bei Suspense alle Ihre Abfragen innerhalb einer Komponente seriell abgefragt werden.

placeholderData existiert für diese Abfrage ebenfalls nicht. Um zu verhindern, dass die Benutzeroberfläche während eines Updates durch einen Fallback ersetzt wird, packen Sie Ihre Updates, die den QueryKey ändern, in startTransition.

throwOnError Standardwert

Nicht alle Fehler werden standardmäßig an die nächste Fehlergrenze geworfen – wir werfen nur Fehler, wenn keine anderen Daten angezeigt werden können. Das bedeutet, wenn eine Abfrage jemals erfolgreich Daten im Cache erhalten hat, wird die Komponente gerendert, auch wenn die Daten stale sind. Daher ist der Standardwert für throwOnError

throwOnError: (error, query) => typeof query.state.data === 'undefined'
throwOnError: (error, query) => typeof query.state.data === 'undefined'

Da Sie throwOnError nicht ändern können (da dies erlauben würde, dass data potenziell undefined wird), müssen Sie Fehler manuell werfen, wenn Sie möchten, dass alle Fehler von Fehlergrenzen behandelt werden.

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

const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })

if (error && !isFetching) {
  throw error
}

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

const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })

if (error && !isFetching) {
  throw error
}

// continue rendering data

Fehlergrenzen zurücksetzen

Unabhängig davon, ob Sie suspense oder throwOnError in Ihren Abfragen verwenden, benötigen Sie eine Möglichkeit, Abfragen mitzuteilen, dass Sie beim erneuten Rendern nach einem Fehler erneut versuchen möchten.

Abfragefehler können mit der Komponente QueryErrorResetBoundary oder dem Hook useQueryErrorResetBoundary zurückgesetzt werden.

Bei Verwendung der Komponente werden alle Abfragefehler innerhalb der Grenzen der Komponente zurückgesetzt.

tsx
import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => (
  <QueryErrorResetBoundary>
    {({ reset }) => (
      <ErrorBoundary
        onReset={reset}
        fallbackRender={({ resetErrorBoundary }) => (
          <div>
            There was an error!
            <Button onClick={() => resetErrorBoundary()}>Try again</Button>
          </div>
        )}
      >
        <Page />
      </ErrorBoundary>
    )}
  </QueryErrorResetBoundary>
)
import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => (
  <QueryErrorResetBoundary>
    {({ reset }) => (
      <ErrorBoundary
        onReset={reset}
        fallbackRender={({ resetErrorBoundary }) => (
          <div>
            There was an error!
            <Button onClick={() => resetErrorBoundary()}>Try again</Button>
          </div>
        )}
      >
        <Page />
      </ErrorBoundary>
    )}
  </QueryErrorResetBoundary>
)

Bei Verwendung des Hooks werden alle Abfragefehler innerhalb der nächstgelegenen QueryErrorResetBoundary zurückgesetzt. Wenn keine Grenze definiert ist, werden sie global zurückgesetzt.

tsx
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => {
  const { reset } = useQueryErrorResetBoundary()
  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ resetErrorBoundary }) => (
        <div>
          There was an error!
          <Button onClick={() => resetErrorBoundary()}>Try again</Button>
        </div>
      )}
    >
      <Page />
    </ErrorBoundary>
  )
}
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => {
  const { reset } = useQueryErrorResetBoundary()
  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ resetErrorBoundary }) => (
        <div>
          There was an error!
          <Button onClick={() => resetErrorBoundary()}>Try again</Button>
        </div>
      )}
    >
      <Page />
    </ErrorBoundary>
  )
}

Fetch-on-render vs Render-as-you-fetch

Out-of-the-Box funktioniert React Query im suspense-Modus gut als **Fetch-on-render**-Lösung ohne zusätzliche Konfiguration. Das bedeutet, dass Ihre Komponenten beim Versuch, gemountet zu werden, die Abfrageabfrage auslösen und pausieren, aber erst, wenn Sie sie importiert und gemountet haben. Wenn Sie die nächste Stufe erreichen und ein **Render-as-you-fetch**-Modell implementieren möchten, empfehlen wir die Implementierung von Prefetching bei Routing-Callbacks und/oder Benutzerinteraktionsereignissen, um Abfragen zu laden, bevor sie gemountet werden, und hoffentlich sogar bevor Sie ihre übergeordneten Komponenten importieren oder mounten.

Suspense auf dem Server mit Streaming

Wenn Sie NextJs verwenden, können Sie unsere **experimentelle** Integration für Suspense auf dem Server nutzen: @tanstack/react-query-next-experimental. Dieses Paket ermöglicht es Ihnen, Daten auf dem Server abzurufen (in einer Client-Komponente), indem Sie einfach useSuspenseQuery in Ihrer Komponente aufrufen. Die Ergebnisse werden dann vom Server zum Client gestreamt, während SuspenseBoundaries aufgelöst werden.

Um dies zu erreichen, umschließen Sie Ihre App mit der Komponente ReactQueryStreamedHydration.

tsx
// app/providers.tsx
'use client'

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // With SSR, we usually want to set some default staleTime
        // above 0 to avoid refetching immediately on the client
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (isServer) {
    // Server: always make a new query client
    return makeQueryClient()
  } else {
    // Browser: make a new query client if we don't already have one
    // This is very important, so we don't re-make a new client if React
    // suspends during the initial render. This may not be needed if we
    // have a suspense boundary BELOW the creation of the query client
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export function Providers(props: { children: React.ReactNode }) {
  // NOTE: Avoid useState when initializing the query client if you don't
  //       have a suspense boundary between this and the code that may
  //       suspend because React will throw away the client on the initial
  //       render if it suspends and there is no boundary
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {props.children}
      </ReactQueryStreamedHydration>
    </QueryClientProvider>
  )
}
// app/providers.tsx
'use client'

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // With SSR, we usually want to set some default staleTime
        // above 0 to avoid refetching immediately on the client
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (isServer) {
    // Server: always make a new query client
    return makeQueryClient()
  } else {
    // Browser: make a new query client if we don't already have one
    // This is very important, so we don't re-make a new client if React
    // suspends during the initial render. This may not be needed if we
    // have a suspense boundary BELOW the creation of the query client
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export function Providers(props: { children: React.ReactNode }) {
  // NOTE: Avoid useState when initializing the query client if you don't
  //       have a suspense boundary between this and the code that may
  //       suspend because React will throw away the client on the initial
  //       render if it suspends and there is no boundary
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {props.children}
      </ReactQueryStreamedHydration>
    </QueryClientProvider>
  )
}

Weitere Informationen finden Sie im NextJs Suspense Streaming Beispiel und im Leitfaden Erweiterte Darstellung & Hydration.

Verwendung von useQuery().promise und React.use() (Experimentell)

Um diese Funktion zu aktivieren, müssen Sie die Option experimental_prefetchInRender auf true setzen, wenn Sie Ihre QueryClient erstellen.

Beispielcode

tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      experimental_prefetchInRender: true,
    },
  },
})
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      experimental_prefetchInRender: true,
    },
  },
})

Verwendung

tsx
import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { fetchTodos, type Todo } from './api'

function TodoList({ query }: { query: UseQueryResult<Todo[]> }) {
  const data = React.use(query.promise)

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

export function App() {
  const query = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

  return (
    <>
      <h1>Todos</h1>
      <React.Suspense fallback={<div>Loading...</div>}>
        <TodoList query={query} />
      </React.Suspense>
    </>
  )
}
import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { fetchTodos, type Todo } from './api'

function TodoList({ query }: { query: UseQueryResult<Todo[]> }) {
  const data = React.use(query.promise)

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

export function App() {
  const query = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

  return (
    <>
      <h1>Todos</h1>
      <React.Suspense fallback={<div>Loading...</div>}>
        <TodoList query={query} />
      </React.Suspense>
    </>
  )
}

Ein vollständigeres Beispiel finden Sie im Suspense-Beispiel auf GitHub.

Ein Beispiel für Next.js-Streaming finden Sie im nextjs-suspense-streaming Beispiel auf GitHub.