iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Suspense

Suspense lets a component "wait" for something — code, data, an image — and render a fallback while it waits. Originally for lazy-loaded components, it now powers data fetching in React 18+ with frameworks like Next, Remix, and any library that integrates with React.use(). The win: declarative loading boundaries instead of every component handling its own spinner.

Suspense for lazy components, images, and data

EXAMPLE
import React, { Suspense, lazy, use } from 'react';

// 1) Code-split a route. The bundle loads on first render of LazyProfile.
const LazyProfile = lazy(() => import('./Profile'));

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <LazyProfile userId='u1' />
    </Suspense>
  );
}

// 2) Suspense for data with the use() hook (React 19+).
//    The component reads a promise — Suspense catches it and shows the fallback.
function UserCard({ userId }) {
  const user = use(fetchUser(userId));   // throws the promise on first call
  return <div>{user.name} — {user.email}</div>;
}

// fetchUser caches by id so re-render does not re-fetch
const userCache = new Map();
function fetchUser(id) {
  if (!userCache.has(id)) {
    userCache.set(id, fetch(\`/api/users/${id}\`).then(r => r.json()));
  }
  return userCache.get(id);
}

// 3) Nested boundaries — independent loading regions
function Dashboard() {
  return (
    <Layout>
      <Suspense fallback={<Skeleton lines={4} />}>
        <UserCard userId='u1' />
      </Suspense>
      <Suspense fallback={<Skeleton lines={8} />}>
        <ActivityFeed />
      </Suspense>
    </Layout>
  );
}

// 4) Streaming SSR (Next.js / React Server Components):
//    The server flushes the shell, then streams each Suspense boundary
//    as its data resolves. The browser paints progressively.
//    export default async function Page() { return (
//      <Suspense fallback={<Spinner/>}>
//        <SlowProductGrid />
//      </Suspense>
//    ); }

// 5) Pair with ErrorBoundary for errors inside the suspended region
//    function Safe({ children }) {
//      return <ErrorBoundary fallback={<Oops/>}><Suspense fallback={<Spinner/>}>{children}</Suspense></ErrorBoundary>;
//    }

// 6) Suspense for images — the new <img> in React 19 also supports it
//    <Suspense fallback={<Skeleton/>}><img src='/hero.jpg' /></Suspense>
//    Useful when you want the layout-shift-free placeholder to share its
//    fallback with surrounding data.

function PageSkeleton() { return <div className='animate-pulse'>Loading...</div>; }
function Skeleton({ lines }) {
  return <div>{Array.from({ length: lines }).map((_, i) => <div key={i} className='skeleton-row'/>)}</div>;
}

Why it matters

Place Suspense boundaries at meaningful regions of the page, not around every component. Too many fallbacks make the page feel janky as little spinners flicker in and out; one or two well-placed boundaries give the user a smooth "shell appears, content fills in" experience.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<Suspense fallback={<Spinner />}>
    <LazyComponent />
</Suspense>
Try it Yourself »

Discussion

Loading…