Chapter 4 of 4

Boundaries with Suspense

Combining loading and error states into one predictable shell.

Loading and failure are two outcomes of the same operation, so their boundaries usually sit together. The error boundary goes outside, because a chunk that fails to download throws rather than suspends.

<ErrorBoundary fallback={<p>Could not load the report.</p>}>
  <Suspense fallback={<ReportSkeleton />}>
    <Report id={reportId} />
  </Suspense>
</ErrorBoundary>

Resetting on retry

import { ErrorBoundary } from "react-error-boundary";

function Section({ id }) {
  return (
    <ErrorBoundary
      resetKeys={[id]}                 // clears the error when id changes
      fallbackRender={({ resetErrorBoundary }) => (
        <div role="alert">
          <p>Could not load this section.</p>
          <button onClick={resetErrorBoundary}>Retry</button>
        </div>
      )}
    >
      <Suspense fallback={<Skeleton />}>
        <Content id={id} />
      </Suspense>
    </ErrorBoundary>
  );
}

A checklist for production

  • A root boundary so nothing ever renders a blank page.
  • A boundary per route and per independent widget.
  • Simple fallbacks that cannot themselves throw.
  • A retry path, and reset keys so navigation clears stale errors.
  • componentDidCatch wired to your monitoring service with the component stack.
  • try/catch in handlers and .catch(setError) for async work, since boundaries never see those.