React

Error boundaries: the pattern that holds

An error boundary is a class component with getDerivedStateFromError: it catches render errors in its subtree and shows a way out.

Key points

  • An error boundary catches only errors thrown during render and lifecycle: event handlers stay outside.
  • The reset runs through a key on the boundary: change it and the component is recreated with fresh state.
  • One boundary per route plus one per external widget covers real cases without multiplying messages.

An error boundary is a React component that catches errors thrown during the render of its subtree and shows a fallback interface in place of the broken component. Errors in an event handler or in a promise do not pass through it.

What goes in and what stays out

  • Render and lifecycle: an error here climbs to the nearest boundary and stops that subtree.
  • Event handlers: they stay outside, so handle them with try and catch or with an error state.
  • Promises and timers: a promise catch is not a render error, and the boundary does not see it.
  • The boundary itself: if the fallback throws, the error climbs to the boundary above.

The minimal component

import { Component } from "react";

export class ErrorBoundary extends Component {
  state = { rotto: false };

  static getDerivedStateFromError() {
    return { rotto: true };
  }

  componentDidCatch(error, info) {
    console.error(error, info.componentStack);
  }

  render() {
    if (this.state.rotto) {
      return this.props.fallback ?? Qualcosa non ha funzionato.;
    }
    return this.props.children;
  }
}

Two methods are enough: getDerivedStateFromError changes state during render, componentDidCatch logs the error and the component stack. The fallback comes from props, so one boundary serves several places.

Reset by key, not by luck

A boundary that stays in its error state never retries on its own. The shape that holds puts a key on the boundary, tied to the route or to an attempt counter: change the key and the component is recreated with fresh state.

How many boundaries

One per route plus one around external widgets. A single boundary at the top of the app turns a local error into a blank page; a boundary per card fills the screen with identical messages.

Live records in the catalogue

The error handling category collects the error states in the catalogue, with the error page variant; a sign-in form shows the case where the error arrives from the network and has to be written by hand.

The limitation

The pattern needs a class: React offers no equivalent hook, because recovery must happen before the commit. In development the boundary does not hide the React error overlay, so the fallback has to be tested in a build.

Where to continue

The App Router error file is in error boundaries in Next.js; what changed in the release is in React 19, what changes; effects that run twice are in useEffect twice in development. The framework comparison is in frontend framework comparison.

Frequently asked questions

Does an error boundary catch errors from a fetch?

No. A rejected promise is not a render error: use a catch that moves the result into state, or a boundary driven by that state.

Why does it need a class instead of a hook?

Because recovery must happen before the commit. Only class methods provide getDerivedStateFromError, which React calls during render.

How do I retry after an error?

Change the boundary key. The new key recreates the component and clears the error state, so the subtree attempts a clean render.

Is one boundary per route better than one global boundary?

One per route is the baseline, with a global boundary as a safety net. A single global boundary turns every local error into a blank page.

Does the boundary work on server components?

No. A boundary is client code; in the App Router the error.tsx file does the same job for a segment, and it receives the error as a prop.