Skip to the content.

Error boundaries stop one broken component from blanking the page

Error boundaries stop one broken component from blanking the page

In a component tree, an uncaught error thrown during render is fatal by default: React unmounts the entire tree rather than show a half-broken UI, so one component reading user.name when user is null blanks the whole page. An error boundary is the seam that stops the blast radius at a subtree. It catches a render-time crash below it, shows a fallback in place of the broken part, and leaves the rest of the app — the nav, the sidebar, the other panels — alive and interactive. Placing boundaries well is the difference between “one widget failed” and “the app is down.”

Without a boundary a throw blanks the app; with one it is contained to a subtree Left: a component throws and the whole app tree goes blank. Right: an error boundary around one panel catches the throw and shows a fallback while siblings stay alive. no boundary whole appblank ✗ with a boundary app (alive) nav ✓ list ✓ boundary:fallback shown
An unhandled throw takes the whole tree down (left). A boundary around the risky panel contains it, and the siblings keep working (right).

A boundary is a component that catches its children’s throws

Error boundaries are a class component (the one place React still needs one), because they use two lifecycle hooks: one to render a fallback, one to report the error. Below it, any render-time throw is caught:

class ErrorBoundary extends React.Component {
  state = { failed: false };
  static getDerivedStateFromError() {
    return { failed: true };            // switch to the fallback UI
  }
  componentDidCatch(error, info) {
    reportToService(error, info);       // log it where you can see it
  }
  render() {
    return this.state.failed
      ? this.props.fallback
      : this.props.children;
  }
}

Wrap a risky subtree and its crash becomes a contained fallback:

<ErrorBoundary fallback={<p>This panel failed to load.</p>}>
  <RevenueChart />     {/* if this throws, only this panel shows the fallback */}
</ErrorBoundary>

Place boundaries at meaningful seams

Where you put boundaries is a design decision about blast radius. One boundary at the very top turns a crash into a whole-app fallback — better than a blank page, but coarse. Boundaries around each independent region — a dashboard’s widgets, a feed’s items, a route — mean a failure in one leaves the others fully usable:

<Dashboard>
  <ErrorBoundary fallback={<WidgetError />}><Revenue /></ErrorBoundary>
  <ErrorBoundary fallback={<WidgetError />}><Traffic /></ErrorBoundary>
</Dashboard>
{/* Revenue crashing does not touch Traffic */}

Know what a boundary does not catch

The important limitation: boundaries catch errors during rendering, lifecycle, and constructors of the components below them. They do not catch errors in event handlers, in async code (setTimeout, promises, fetch callbacks), or in the boundary’s own render — because those do not happen during React’s render pass. An error in an onClick you handle with a plain try/catch and, say, a toast; a failed fetch you handle with the request/success/fail state and an error UI. So the full resilience story is two-layered: boundaries for the render-path crashes that would otherwise blank the tree, and ordinary error handling for the async and event-driven failures that a boundary never sees. The toast-notifications and retry-with-backoff exercises cover that second layer — the errors boundaries leave for you — which together with a well-placed boundary is what keeps an app standing when something inevitably throws.