Skip to content

With Suspense

Using React Suspense for data loading states.

Basic Suspense

tsx
import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTreaty } from './lib/treaty'

function UserList() {
  const treaty = useTreaty()

  // Data is guaranteed to be available
  const { data } = useSuspenseQuery(
    treaty.api.users.queryOptions()
  )

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

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

Multiple Suspense Boundaries

tsx
function UserProfile({ userId }: { userId: string }) {
  const treaty = useTreaty()

  const { data: user } = useSuspenseQuery(
    treaty.api.users({ id: userId }).queryOptions()
  )

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  )
}

function Dashboard() {
  return (
    <div>
      <Suspense fallback={<UserListSkeleton />}>
        <UserList />
      </Suspense>

      <Suspense fallback={<ProfileSkeleton />}>
        <UserProfile userId="1" />
      </Suspense>
    </div>
  )
}

With Error Boundary

tsx
import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <Suspense fallback={<Loading />}>
        <UserList />
      </Suspense>
    </ErrorBoundary>
  )
}

Suspense Infinite Query

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

function InfiniteList() {
  const treaty = useTreaty()

  const { data, fetchNextPage, hasNextPage } = useSuspenseInfiniteQuery(
    treaty.api.posts.infiniteQueryOptions(
      { query: { limit: 10 } },
      {
        initialCursor: 0,
        getNextPageParam: (lastPage) => lastPage.nextCursor
      }
    )
  )

  return (
    <div>
      {data.pages.flatMap(page =>
        page.items.map(item => (
          <article key={item.id}>{item.title}</article>
        ))
      )}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()}>Load More</button>
      )}
    </div>
  )
}

Released under the Apache 2.0 License.