skip to content

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