Suspense

Declarative Loading States

Suspense

`<Suspense>` lets a component "wait" for something — and shows a fallback in the meantime. Used for lazy components and (with the right libraries) data.

4 min read Level 3/5 #react#suspense#loading
What you'll learn
  • Wrap lazy components with `<Suspense>`
  • Understand what "suspending" means
  • Recognize when to use Suspense for data

<Suspense> is a declarative way to say “if anything inside here isn’t ready yet, show this fallback”. Like an error boundary, but for “not ready” instead of “broken”.

The Shape

import { Suspense } from "react";

<Suspense fallback={<Spinner />}>
  <UserProfile />
</Suspense>

If <UserProfile> suspends (waits for data or code that isn’t ready), React renders <Spinner /> until it’s ready, then swaps in the real content.

What “Suspending” Means

A component suspends by throwing a promise. React catches the throw, shows the nearest <Suspense> fallback, and re-renders the suspended component when the promise resolves.

You don’t write throw promise yourself — you use a library or hook that does it for you:

  • React.lazy() for code-splitting (next lesson)
  • The use(promise) hook in newer React
  • Suspense-enabled data libraries (Relay, React Query with Suspense mode)

Code-Splitting Example

const Settings = lazy(() => import("./Settings.jsx"));

function App() {
  return (
    <Suspense fallback={<p>Loading settings…</p>}>
      <Settings />
    </Suspense>
  );
}

The first time <Settings> renders, React kicks off the dynamic import. The fallback shows until the chunk arrives. After that, it renders normally.

Suspense for Data (Newer)

With use(promise) and Suspense-enabled data libraries:

function UserProfile({ promise }) {
  const user = use(promise);   // suspends until resolved
  return <p>{user.name}</p>;
}

<Suspense fallback={<Spinner />}>
  <UserProfile promise={fetchUser(id)} />
</Suspense>

This is the pattern React Server Components and the latest data libraries are leaning into.

Pairing With Error Boundaries

Suspense handles “not ready”; error boundaries handle “broken”. Together they give you a complete UX:

<ErrorBoundary fallback={<ErrorScreen />}>
  <Suspense fallback={<Spinner />}>
    <Dashboard />
  </Suspense>
</ErrorBoundary>

Loading → spinner. Error → error screen. Success → real content.

Up Next

The most common Suspense use case — splitting your bundle.

React.lazy →