ESC

Type to search the knowledge base.

Error Boundaries

Class error boundaries catch render errors in the tree, show fallback UI, and what they do not catch: events, async, own render.

intermediate4 min read
  • react
  • error-boundaries

A child throws during render. Without an error boundary, React unmounts the whole tree down to the root and you get a blank page or the framework error overlay. Error boundaries are class components that catch those errors in their subtree, log them, and render fallback UI so the rest of the app survives.

They are still class-only (getDerivedStateFromError / componentDidCatch). There is no first-party hook for this in core React — libraries rethrow into a boundary to simulate one.

Docs: Error Boundaries.

What they catch (and what they do not)

Caught Not caught
Errors in render of children Event handlers (onClick)
Lifecycle methods of children Async code (setTimeout, promises) after render
Constructors of child class components Server-only failures outside the client tree
Errors in components below the boundary The boundary’s own render (need a parent boundary)

For event handlers and async, use try/catch locally and set error state — or deliberately throw during a later render after storing an error so a boundary can handle it.

Minimal boundary

import { Component } from 'react';

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

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

  componentDidCatch(error, info) {
    console.error('UI crash', error, info.componentStack);
    // report to your error service
  }

  render() {
    if (this.state.error) {
      return (
        <div role="alert">
          <h2>Something went wrong</h2>
          <button type="button" onClick={() => this.setState({ error: null })}>
            Try again
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

function App() {
  return (
    <ErrorBoundary>
      <Dashboard />
    </ErrorBoundary>
  );
}

Place boundaries around independent product surfaces (sidebar vs main, widget vs page), not only at the root. Root-only means one bad chart kills the entire shell.

Recovery patterns

Remount with key so child state resets on retry:

function Panel() {
  const [resetKey, setResetKey] = useState(0);
  return (
    <ErrorBoundary key={resetKey}>
      <RiskyWidget onGiveUp={() => setResetKey((k) => k + 1)} />
    </ErrorBoundary>
  );
}

Framework UI: In Next.js App Router, error.tsx is a client boundary for a route segment — same idea as a class boundary, with a file convention. Pair with loading UI.

Data libraries: Network failures are not render throws unless you throw them. React Query error states sit beside boundaries by default.

SSR / RSC: Server render errors need server logging and route-level error UI. Client boundaries only protect client render after hydration.

Footguns

  • Expecting onClick throws to be caught — wrap the handler.
  • Boundary too high → huge fallback, lost app chrome.
  • Logging only in getDerivedStateFromError — keep that method pure; side effects belong in componentDidCatch.
  • Showing raw error.message to end users — often internal; prefer generic copy plus a support id.

Interview out-loud

“Error boundaries are class components that implement getDerivedStateFromError and componentDidCatch. They catch render and lifecycle errors below them and show fallback UI. They do not catch event handlers, async, or their own errors. In Next App Router, error.tsx wraps a segment similarly on the client.”

Further reading

Edge cases worth rehearsing

Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.

Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.

When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.

Related guides