Error Boundaries and Suspense
Catching render errors and showing fallbacks while content loads.
What you'll learn
- What happens when a component throws during render
- Writing an error boundary, and where to put it
- What boundaries do not catch
- Showing a fallback while a component loads, with
Suspense
An error unmounts everything
If a component throws while rendering, React unmounts the whole tree rather than leave a half-rendered screen. One broken widget takes the entire page with it, which is why an unguarded app goes blank instead of showing a broken corner.
In a real app that means a blank screen. The sandboxes on this page are kinder: when a render error goes uncaught, Sandpack replaces the preview with a "Something went wrong" panel naming the file and line. Read that panel as "the app would be blank here" — the component tree is gone either way, and only the tooling is telling you why.
An error boundary
A boundary is a component that catches errors thrown by its descendants during render and shows a fallback instead. It is the one thing in modern React that still must be a class component — there is no hook equivalent, because the two lifecycle methods it needs have none.
Break the widget: the fallback replaces it, and the button and paragraph around it are untouched. That is the point of a boundary — it contains the damage.
The two methods do different jobs. getDerivedStateFromError returns the new
state so the fallback renders; componentDidCatch is where you log the error.
Most projects write one boundary and reuse it, or install
react-error-boundary rather
than hand-writing the class.
Where to put them
Not one at the root. A single boundary around your whole app is the blank page again, only with nicer wording.
Put boundaries where a failure should be contained: around each widget on a dashboard, around a route, around anything rendering data you do not control. Granularity is the whole benefit.
What boundaries do not catch
This surprises people, and it is the reason a boundary can look like it is not working. Boundaries catch errors during rendering, in lifecycle methods, and in constructors below them. They do not catch:
- Errors in event handlers — use
try/catchthere - Errors in asynchronous code, including
setTimeoutand promise callbacks - Errors thrown in the boundary itself
- Errors during server-side rendering
The handler error is caught by its own try/catch and logged; the boundary is
never involved. The render error replaces the component.
Suspense
Suspense is the same idea for waiting rather than failing: it shows a
fallback while something below it is not ready.
The case that works anywhere is code splitting. lazy defers loading a
component's code until it is rendered, and Suspense covers the gap:
Suspense also covers data loading, but not on its own: a component has to be
written to suspend, which in practice means a framework or a library that
supports it. lazy is the part you can use in any React app today.
Suspense and error boundaries are two halves of the same job — a fallback
while loading, a fallback when it fails — and they nest together, boundary
outside:
<ErrorBoundary>
<Suspense fallback={<Spinner />}>
<Profile />
</Suspense>
</ErrorBoundary>
Common mistakes
- One boundary at the root. A blank page with better wording.
- Expecting handler errors to be caught. Use
try/catchthere. - No way out of the fallback. Give the user a retry, as above.
- A boundary that can throw. Keep the fallback trivially simple.
Exercise
The page below renders fine until you press the button, which adds a profile
that has no name. ProfileCard throws on it, and because nothing catches the
error the whole preview goes — heading, button and Ada's card included.
Wrap the incomplete profile in the provided ErrorBoundary so the failure is
contained. Then pressing the button should leave the rest of the page standing
and show the fallback in place of the broken card.