← Back to React Course | Chapter 13: Advanced React & Architecture | Lesson 4 of 14

React Suspense

React Suspense is like a 'please wait' sign a component can hang on its own door while it's not ready yet, letting a parent show a nice loading message instead of a blank room.

What Suspense Does

Suspense lets a component pause rendering and show a fallback UI (like a spinner) instead, until whatever it's waiting for is ready. The most common, stable use of this today is React.lazy() for code-splitting, where Suspense shows a fallback while the component's code is still downloading.

Note: Think of Suspense as a specialized, built-in loading-state mechanism, rather than a general-purpose replacement for all async handling.

Warning: Suspense's fallback prop is required — without it, there's nothing to show while the wrapped content isn't ready.

Example: What Suspense Does

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
const LazyComponent = React.lazy(() =>
  Promise.resolve({ default: () => <p>I loaded (simulated lazy import)</p> })
);
function App() {
  return (
    <React.Suspense fallback={<p>Loading...</p>}>
      <LazyComponent />
    </React.Suspense>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

One Fallback for Multiple Suspending Children

A single Suspense boundary can wrap several lazy-loaded (or otherwise suspending) children at once. The fallback shows if ANY of them aren't ready yet, and the actual content only appears once ALL of them have finished loading.

Note: Group related lazily-loaded pieces under one Suspense boundary when you want them to appear together, rather than popping in one at a time.

Warning: If one child takes much longer than the others, the whole group stays on the fallback until the slowest one finishes — consider separate boundaries if that's not desired.

Example: One Fallback for Multiple Suspending Children

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
const ComponentA = React.lazy(() => Promise.resolve({ default: () => <p>A loaded</p> }));
const ComponentB = React.lazy(() => Promise.resolve({ default: () => <p>B loaded</p> }));
function App() {
  return (
    <React.Suspense fallback={<p>Loading both...</p>}>
      <ComponentA />
      <ComponentB />
    </React.Suspense>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Suspense Is Not a General Async Replacement

Suspense doesn't automatically work with any Promise or arbitrary async code — it requires something specifically built to integrate with it, like React.lazy for components, or a data-fetching library with explicit Suspense support. A plain fetch() inside useEffect doesn't suspend on its own.

Note: For plain data fetching without a Suspense-compatible library, stick to the loading/error state patterns from the useFetch/useEffect tutorials instead.

Warning: Wrapping a component that does a normal useEffect-based fetch in Suspense does nothing useful — Suspense won't detect or wait for that fetch at all.

Example: Suspense Is Not a General Async Replacement

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
// This does NOT integrate with Suspense automatically:
function NormalFetcher() {
  const [data, setData] = React.useState(null);
  React.useEffect(() => { setData("loaded"); }, []);
  return <p>{data || "loading (Suspense doesn't see this)"}</p>;
}
function App() {
  return <React.Suspense fallback={<p>This fallback won't show for the above</p>}><NormalFetcher /></React.Suspense>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Trying to use Suspense for regular data fetching (like a fetch() call in useEffect) — that's not what it's built for without a Suspense-compatible data library.
  2. Forgetting the fallback prop is required — Suspense needs to know what to show while waiting.
  3. Not understanding Suspense only works with things specifically designed to suspend (like React.lazy or Suspense-enabled data libraries), not arbitrary async code.
Chapter Summary
  • Suspense lets a component tree show a fallback UI while something inside it isn't ready yet.
  • The most common, stable use case is code-splitting with React.lazy, showing a fallback while a chunk downloads.
  • Suspense for data fetching exists but requires a library specifically built to support it (like React Query's or Relay's Suspense integration).
  • A single Suspense boundary can wrap multiple lazy/suspending children, showing one shared fallback.
Browser Support

Suspense for lazy-loaded components stable since React 16.6; Suspense for data fetching is a more recent, evolving feature (React 18+).

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.