React Error Boundaries Explained

Hero image for React Error Boundaries Explained. Image by Alejandro Barba.
Hero image for 'React Error Boundaries Explained.' Image by Alejandro Barba.

Few things make a React interface feel more fragile than one rendering error taking down an entire section of the page, or worse, the whole application.

Before error boundaries became part of React, a component error could leave the UI in a poor state with very little graceful recovery. Error boundaries improved that by giving us a way to catch rendering errors in part of the tree and replace the broken subtree with fallback UI.

That is the practical value of the feature. It is not about hiding bugs. It is about containing failure.


An Error Boundary Wraps Part of the Tree

If a descendant component throws during rendering, in a lifecycle method, or in its constructor, the boundary can catch that failure and render an alternative interface instead.

This is what the pattern looks like:

type ErrorBoundaryState = {  hasError: boolean;};class ErrorBoundary extends React.Component<  { children: React.ReactNode },  ErrorBoundaryState> {  public state: ErrorBoundaryState = {    hasError: false,  };  public static getDerivedStateFromError(): ErrorBoundaryState {    return {      hasError: true,    };  }  public componentDidCatch(error: Error): void {    console.error(error);  }  public render(): React.ReactNode {    if (this.state.hasError) {      return <p>Something went wrong in this section.</p>;    }    return this.props.children;  }}

That boundary can then wrap a risky or isolated part of the UI.

Using it is usually straightforward:

const ProductPage = (): JSX.Element => {  return (    <>      <ProductSummary />      <ErrorBoundary>        <ReviewsPanel />      </ErrorBoundary>      <RelatedProducts />    </>  );};

That placement matters because it expresses intent clearly. If ReviewsPanel breaks, the review section can fail gracefully without taking the product summary and related products down with it.


Why This Matters in Real Applications

Not every part of a page deserves the same failure behaviour.

If a reviews widget fails, that should not necessarily take down the whole product page. If one dashboard panel breaks, the rest of the dashboard may still be useful.

Error boundaries let us define those containment lines more deliberately.

That improves resilience, but it also improves user trust. A partially degraded interface is often far better than a blank or broken one.


The Fallback UI Should Be Honest

This is easy to overlook.

A good fallback does not pretend the feature worked. It tells the user something went wrong and, where appropriate, offers a next step:

  • retry
  • refresh
  • continue using the rest of the page
  • contact support

The exact copy depends on the feature. The important part is honesty. Error boundaries are about graceful failure, not about disguising failure.


componentDidCatch() and getDerivedStateFromError() do different jobs

This distinction is worth understanding.

getDerivedStateFromError() updates state so the boundary can switch to fallback UI.

componentDidCatch() is the place for side effects such as logging.

That means a sensible boundary often uses both:

  • one for changing what is rendered
  • one for reporting what happened

Keeping those responsibilities separate makes the component easier to reason about.


Error Boundaries Do Not catch Everything

This is the part developers usually need to learn quickly.

Error boundaries do not catch errors from:

  • event handlers
  • asynchronous callbacks like setTimeout
  • serverside rendering
  • errors thrown inside the boundary itself

That list matters because people often expect a boundary to work like a global try/catch for the whole app. It is not that.

If an error happens in a click handler, for example, ordinary JavaScript error handling is still needed. The boundary only helps with renderphase and lifecyclerelated failures in the component tree beneath it.


Placement Strategy Matters

Wrapping the entire application in one giant error boundary is possible, but it is rarely the most helpful arrangement on its own.

If the entire app is inside a single boundary, one component failure may replace the whole interface with one fallback screen.

That can still be useful as a lastresort safety net. But many applications also benefit from smaller boundaries around isolated sections:

  • route content
  • sidebars
  • dashboard panels
  • widgets
  • thirdparty integrations

This is where the feature becomes architectural rather than just defensive.


Boundaries are Especially Useful Around Less Trusted Components

Some parts of the tree are more failureprone than others:

  • newly migrated code
  • complicated data rendering
  • thirdparty wrappers
  • unstable feature areas

Those are natural candidates for local boundaries because they let the rest of the page survive if that section fails.


They are Not a Substitute for Fixing Bugs

This should be obvious, but it is still worth stating.

An error boundary is not permission to leave fragile code in place. It is there to protect the user experience while we fix issues properly and gather useful logs.

If a boundary keeps firing in production, that is a signal that the underlying problem still needs attention.


The Component is Class‑Based for a Reason

At the time of writing, error boundaries are implemented with class component APIs.

That can look slightly awkward in an application increasingly built with function components, but it is not a contradiction. The boundary can still wrap function components perfectly well. The boundary itself just happens to be written as a class.

This is a good reminder that React features do not all arrive in one stylistic era at once.


Logging is Part of the Value

If an error boundary only renders fallback UI and discards the actual error details, half the opportunity is lost.

componentDidCatch() is the obvious place to connect to logging, diagnostics, or reporting tools. That turns the boundary from a silent bandage into an observability point.

Containing failure for the user is useful. Learning why it happened is just as useful for the team.


Think of Error Boundaries as Fault Lines

That is the most useful mental model.

They mark the places where one part of the UI is allowed to fail without dragging unrelated parts down with it.

When those lines are placed well, the application feels sturdier. When they are placed badly, either too much survives in a broken state or too much gets taken down unnecessarily.


They Improve Resilience by Making Failure Local

That is the real lesson.

React error boundaries are not glamorous, but they are one of the clearer examples of the framework encouraging better operational thinking. Failures happen. The question is whether one failure should be allowed to wipe out everything around it.

Usually, the answer is no.

Used thoughtfully, error boundaries let us turn that answer into actual interface behaviour.


Categories:

  1. Architecture
  2. Development
  3. Front‑End Development
  4. Guides
  5. JavaScript
  6. React