skip to content

Error Boundaries

How React contains a render-time crash to one subtree and shows a fallback instead of unmounting the whole app — and the equally important list of what boundaries do not catch. Expect 'why is this still a class component?' and 'so how do you catch an error in an async handler?'

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In React, what does an error boundary do, and what happens when a component throws during render and there is no error boundary above it?

level: juniorimportance: must knowfreq 72%

answer

  1. try/catch, but for rendering
  2. the nearest one above catches
  3. subtree unmounts, siblings survive
  4. nothing catches means blank page
  5. boundary cannot catch itself

basics

~20 s

An error boundary catches an error thrown while rendering anywhere in its subtree and shows fallback UI instead of that subtree. With no boundary above the throwing component, React unmounts the entire tree and the page goes blank.

solid answer

~50 s

An error boundary is a component that wraps part of the tree and, when a descendant throws during rendering, in a constructor, in a lifecycle method, or in an effect, renders a fallback instead of letting the failure escape. It is React's `try`/`catch` for the render path: the nearest boundary *above* the throwing component takes over, everything below that boundary is unmounted, and the rest of the app keeps running. If nothing catches the error, React deliberately unmounts the whole root — the reasoning is that a corrupted, half-rendered UI is more dangerous than no UI, because a user may act on stale or wrong data. React 19 also lets you pass `onUncaughtError` and `onCaughtError` to `createRoot` so both cases reach your logging service from one place. In practice you wrap each independently-failing region so one broken widget cannot take the page down.

code

jsx · 27 lines
jsx
import { Component } from 'react';

class ErrorBoundary extends Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };
  }

  render() {
    if (this.state.error) {
      return <p role="alert">This section could not be displayed.</p>;
    }
    return this.props.children;
  }
}

function Page() {
  return (
    <main>
      <ErrorBoundary>
        <Chart />
      </ErrorBoundary>
      <Table />
    </main>
  );
}

go deeper

for a junior

Be able to say plainly that an error boundary shows fallback UI when something below it throws while rendering, and that with no boundary anywhere React unmounts the whole app and the screen goes blank.

for a middle

Explain the propagation rule: the error travels up to the nearest boundary above the throwing component, that subtree unmounts, the boundary itself stays mounted, and a boundary never catches its own render error.

for a senior

Show judgment about blast radius and data loss — a boundary around a partly filled form discards the user's input — and insist that every boundary reports, so contained errors still reach your monitoring instead of vanishing.

for a principal

Own the policy: what the root-level onUncaughtError/onCaughtError hooks feed, what an acceptable rate of contained failures is, and how the team avoids boundaries becoming a way to make broken features look healthy.

## The problem an error boundary solves React renders by calling your components as functions. If a component throws while React is calling it — reading `user.name` when `user` is `undefined`, mapping over a list that came back `null` — that exception unwinds through React's own work loop, in the middle of building a new tree. React cannot leave a half-constructed tree on screen and it cannot pretend the component rendered, so it needs a defined recovery policy. That policy is the error boundary. An error boundary is a component that declares itself able to handle errors from its subtree. When a descendant throws, React walks *up* from the failing component to the nearest such boundary, unmounts everything below that boundary, and renders the boundary's fallback in its place. The boundary itself stays mounted; its children do not. ## What counts as a catchable error Boundaries cover the errors React itself triggers while driving your components: - errors thrown during rendering (the function body of a component, or a class `render`), - errors thrown in a class constructor, - errors thrown in lifecycle methods, - errors thrown inside an effect body or an effect cleanup function. They do **not** cover code React did not call as part of that lifecycle — an `onClick` handler, a `setTimeout` callback, a `.then` callback, or anything on the server-rendering pass. That list has its own discipline and is worth learning separately; the short version is that a boundary is a render-path mechanism, not a global exception handler. ## The nearest boundary wins Boundaries nest. If a `<Dashboard>` boundary wraps a `<ChartPanel>` boundary and a component inside `ChartPanel` throws, the inner boundary catches it and only the chart panel is replaced. If the *inner boundary's own* render throws, it cannot catch itself — the error continues up to `Dashboard`. This is exactly the semantics of nested `try`/`catch`: a `catch` block that throws does not re-enter itself. ```jsx <AppBoundary> {/* catches anything the route boundary misses */} <RouteBoundary> {/* catches this page */} <ChartBoundary> {/* catches only the chart */} <Chart /> </ChartBoundary> <Table /> </RouteBoundary> </AppBoundary> ``` A throw in `<Chart />` leaves `<Table />` untouched. A throw in `<Table />` replaces the whole page but keeps the app shell. ## Why an uncaught error unmounts everything Newcomers expect React to skip the broken component and render the rest. It does the opposite: since React 16, an error that reaches the root unmounts the entire tree, and you get a blank page. The rationale is that a UI is a projection of state, and an exception means the projection failed partway through. Leaving whatever happened to be committed earlier on screen produces a screen nobody designed: a banking page that still shows the old balance next to a live "Transfer" button, a checkout that lost its total but kept its "Pay" action. React's position is that showing nothing is safer than showing something wrong, and that the correct fix is for the application to declare where degradation is acceptable — which is what a boundary is. ## What the user actually loses When a boundary catches, everything under it unmounts: component state is discarded, effect cleanups run, DOM nodes are removed. A boundary placed around a half-filled form throws the user's typing away along with the error. That is a placement consideration, not a defect in the mechanism — the boundary should sit where losing the subtree is an acceptable outcome. ## Reporting in React 19 React 19 tightened error reporting. Instead of relying on duplicated console output, the root accepts callbacks: ```jsx createRoot(container, { onCaughtError: (error, errorInfo) => report('caught', error, errorInfo.componentStack), onUncaughtError: (error, errorInfo) => report('fatal', error, errorInfo.componentStack), }); ``` `onCaughtError` fires when a boundary handled the error; `onUncaughtError` fires when none did and the tree is being torn down. Wiring both gives you one reporting site for the whole app, independent of how many boundaries you have. (A third callback, `onRecoverableError`, exists for errors React recovered from on its own, such as certain hydration problems.) ## The mental model to carry into an interview An error boundary does not make errors go away; it decides how far a failure spreads. You choose the blast radius by choosing where the boundary sits, and you keep the failure visible by reporting it. A boundary with no reporting attached is a bug-hiding device.

  • When a boundary catches, is the boundary component itself replaced too?
    No. The boundary stays mounted and re-renders with its own error state, which is what lets it show the fallback. Everything *below* it is unmounted: child state is discarded, effect cleanups run, and the DOM under it is removed. That is why the fallback must live in the boundary, not in the failing child.
  • Two boundaries are nested and the inner one's own render throws. What happens?
    A boundary cannot catch its own error, so it propagates to the next boundary above — the outer one — which replaces the inner boundary and everything under it. This is why fallback UI should be trivial and dependency-free: a fallback that throws escalates the failure instead of containing it.
  • Why doesn't React just skip the broken component and render its siblings?
    Because render output is a projection of state, and a throw means that projection failed partway. Partial UI can be actively misleading — stale numbers beside live buttons — so React treats an uncaught error as fatal and unmounts the root, forcing the app to declare where degradation is acceptable by placing boundaries.

saying these in an interview costs you the question

  • Thinks an uncaught error just leaves the last UI on screen
  • Believes React ships a default visible fallback for crashes
  • Says a boundary catches errors thrown in its own render
  • Assumes children keep their state after the fallback shows
  • Thinks one root boundary makes the app crash-proof

context

open as a page

Which failures does a React error boundary NOT catch, and how do you get those errors in front of a boundary anyway?

level: middleimportance: must knowfreq 62%

basics

~20 s

React error boundaries only catch errors React itself throws while rendering, in constructors, in lifecycle methods, and in effects. They miss event handlers, asynchronous callbacks, server rendering, and the boundary's own errors. The fix is to catch such errors and rethrow them during render.

open as a page

In a React class error boundary, what is the difference between static getDerivedStateFromError(error) and componentDidCatch(error, errorInfo), and what belongs in each?

level: middleimportance: must knowfreq 66%

basics

~20 s

getDerivedStateFromError is a static, pure, render-phase method that returns a state patch so the boundary renders its fallback. componentDidCatch runs after the commit and is where side effects such as logging belong. Most boundaries implement both.

open as a page

React 19 still ships no built-in hook for creating an error boundary. Why must a boundary be a class component, and how do hooks-only codebases deal with that?

level: middleimportance: should knowfreq 44%

basics

~20 s

React exposes error catching only through the class methods getDerivedStateFromError and componentDidCatch; no hook equivalent exists in React 19. Teams write one small boundary class and reuse it, or adopt the react-error-boundary package, which wraps that class in a hook-friendly API.

open as a page

A React error boundary is showing its fallback and the user clicks "Try again". What has to happen for the subtree to genuinely recover, and why does merely clearing the boundary's error state often fail?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Recovery needs two steps: clear the boundary's error state so children render again, and clear whatever caused the failure. Clearing only the error re-renders the same inputs, so the component throws immediately and the fallback returns — often in a tight loop.

open as a page

How do you decide how many error boundaries a React app needs and where they go, given that a single boundary at the root already prevents every blank page?

level: principalimportance: should knowfreq 34%

basics

~20 s

Place boundaries by blast radius: a root boundary as the last resort, one per route so navigation recovers, and one around each region whose failure should not remove the page's primary task. Every boundary must report, or it becomes a way to hide bugs.

open as a page