Intermediate0 questionsFull Guide

React Error Boundaries — Complete Interview Guide

Master React Error Boundaries — how they catch render errors, the difference between getDerivedStateFromError and componentDidCatch, what they do and don't catch, and how to build resilient UIs that survive component crashes.

The Mental Model

An Error Boundary is React's circuit breaker. Without one, a JavaScript error thrown during rendering propagates up through every parent component and unmounts the entire application — the screen goes blank. An Error Boundary wraps a subtree and intercepts errors before they escape, showing a fallback UI while keeping the rest of the app alive. Think of it as blast containment: one widget crashes, the page survives.

The Explanation

The Problem Without Error Boundaries

React's default behaviour when a component throws during rendering: the error propagates up the tree, React unwinds every parent component, and the entire application unmounts. Your users see a blank white screen with nothing in the console except a stack trace.

This is intentional — React would rather show nothing than a broken, half-rendered UI. But "nothing" is a terrible user experience. Error Boundaries let you replace "nothing" with a graceful fallback for just the broken section, while the rest of the app keeps running.

The Two Lifecycle Methods

Error Boundaries use two lifecycle methods that have no hooks equivalent — this is the one case in modern React where you must write a class component:

static getDerivedStateFromError(error)

Called during the render phase when a child throws. Its job is purely to update state so the next render shows the fallback UI. It must be a pure function — no side effects.

static getDerivedStateFromError(error) {
  // Return a state update — this triggers a re-render with the fallback
  return { hasError: true, error };
}

componentDidCatch(error, info)

Called during the commit phase after the fallback UI has rendered. This is where you log the error to an external service. Unlike getDerivedStateFromError, side effects are safe here.

componentDidCatch(error, info) {
  // info.componentStack shows the component tree that led to the error
  logErrorToSentry(error, { componentStack: info.componentStack });
}

A Complete Error Boundary

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

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

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <div>Something went wrong.</div>;
    }
    return this.props.children;
  }
}

What Error Boundaries Catch

Error Boundaries catch errors that happen in:

  • The render method of a child component
  • Lifecycle methods (componentDidMount, componentDidUpdate, etc.)
  • Constructors of child class components

What Error Boundaries Do NOT Catch

Error Boundaries cannot catch errors from:

  • Event handlers — use try/catch inside your handler instead
  • Async code — setTimeout, fetch, Promises that reject after the render phase
  • Server-side rendering
  • Errors inside the Error Boundary itself — only errors in its children
// ❌ Error Boundary WON'T catch this
function Button() {
  function handleClick() {
    throw new Error("oops"); // event handler — use try/catch here
  }
  return <button onClick={handleClick}>Click</button>;
}

// ✅ Handle event handler errors with try/catch
function Button() {
  function handleClick() {
    try {
      riskyOperation();
    } catch (err) {
      setError(err.message);
    }
  }
  return <button onClick={handleClick}>Click</button>;
}

Granularity — Where to Place Error Boundaries

Place Error Boundaries at the right level of granularity. Too high (one at the app root) and a broken widget takes down everything visible. Too low (one on every component) and you add unnecessary overhead.

// Route-level: each page is isolated — one page crashing doesn't affect others
<ErrorBoundary fallback={<ErrorPage />}>
  <Routes />
</ErrorBoundary>

// Widget-level: a broken chart doesn't break the whole dashboard
<ErrorBoundary fallback={<p>Chart unavailable</p>}>
  <RevenueChart />
</ErrorBoundary>

Error Recovery — Resetting the Boundary

After an error, you can offer the user a way to retry by resetting the error state. The standard pattern is a "Try again" button that clears hasError:

render() {
  if (this.state.hasError) {
    return (
      <div>
        <p>Something went wrong.</p>
        <button onClick={() => this.setState({ hasError: false, error: null })}>
          Try again
        </button>
      </div>
    );
  }
  return this.props.children;
}

The react-error-boundary Library

Writing Error Boundary classes is boilerplate. The react-error-boundary library provides a ready-made <ErrorBoundary> component and a useErrorBoundary() hook that lets you programmatically trigger the boundary from inside a functional component (e.g. after a failed async operation):

import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';

// Wrap any subtree
<ErrorBoundary
  fallbackRender={({ error, resetErrorBoundary }) => (
    <div>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )}
  onError={(error, info) => logToSentry(error, info)}
  onReset={() => refetch()} // called when resetErrorBoundary fires
>
  <MyFeature />
</ErrorBoundary>

Common Misconceptions

⚠️

Many developers think Error Boundaries catch all React errors — they only catch errors thrown during rendering, lifecycle methods, and constructors of child components. Errors in event handlers, async callbacks, and setTimeout are invisible to Error Boundaries and need try/catch.

⚠️

Many developers think you can implement Error Boundaries with hooks — there is no hooks equivalent for getDerivedStateFromError and componentDidCatch. Error Boundaries must be class components. This is the last remaining use case that requires a class component in modern React.

⚠️

Many developers place one Error Boundary at the app root and consider it done — a root-level boundary only prevents the blank screen; it doesn't provide meaningful isolation. Widget-level boundaries ensure a broken chart doesn't kill a dashboard, a broken sidebar doesn't kill a page.

⚠️

Many developers think getDerivedStateFromError and componentDidCatch do the same thing — getDerivedStateFromError runs during the render phase and must be pure (its only job is to update state). componentDidCatch runs after the commit phase and is the right place for side effects like logging.

⚠️

Many developers try to log errors in getDerivedStateFromError — logging is a side effect and getDerivedStateFromError runs in the render phase where side effects are unsafe (React may call it multiple times in concurrent mode). Always log in componentDidCatch.

⚠️

Many developers think Error Boundaries make async errors safe — a fetch() that rejects after the render phase is not caught by an Error Boundary. For async error handling inside functional components, use the useErrorBoundary() hook from react-error-boundary to programmatically throw errors into the boundary.

Where You'll See This in Real Code

Route-level boundaries in SPAs: wrapping each route with an Error Boundary means a crash in the /settings page doesn't break the /dashboard page — users can navigate away and keep working.

Widget isolation in dashboards: analytics dashboards wrap each chart or data card in an Error Boundary so a broken third-party chart library doesn't crash the entire reporting page.

Error logging with Sentry/Datadog: componentDidCatch is the standard integration point for error monitoring services — it receives the full error object and component stack trace for remote logging without the user seeing a broken UI.

React Query integration: when a query throws during rendering, wrapping the component in an Error Boundary with throwOnError: true provides clean fallback UI without manually checking error state in every component.

React Suspense pairing: Error Boundaries and Suspense are always used together when using React.lazy — Suspense handles the loading state, ErrorBoundary handles the failed load (network error, chunk failed to parse).

Retry buttons in production UIs: the reset pattern (clearing hasError on button click, plus calling the onReset callback) lets users recover from transient errors like network blips without a full page reload.

Interview Cheat Sheet

  • Error Boundaries catch: render errors, lifecycle errors, constructor errors in child components
  • Error Boundaries do NOT catch: event handler errors, async errors (setTimeout, fetch), errors in the boundary itself
  • Must be a class component — no hooks equivalent exists for getDerivedStateFromError / componentDidCatch
  • getDerivedStateFromError: render phase, return state update to show fallback, NO side effects
  • componentDidCatch: commit phase, safe for side effects like logging to Sentry/Datadog
  • Event handler errors: use try/catch inside the handler, set error state manually
  • Granularity: route-level for page isolation, widget-level for dashboard/feature isolation
  • Recovery: reset hasError state on button click; use onReset callback to refetch/retry
  • react-error-boundary library: provides ready-made component + useErrorBoundary() hook for programmatic errors
💡

How to Answer in an Interview

  • 1.Lead with the problem they solve: 'Without an Error Boundary, any render error propagates to the root and unmounts the entire app — the user sees a blank screen. Error Boundaries intercept the error and show a fallback UI so the rest of the app keeps running.'
  • 2.The 'why must it be a class component?' question is common — answer directly: 'getDerivedStateFromError and componentDidCatch have no hooks equivalent. React hasn't provided a hooks API for them yet. Error Boundaries are the last remaining use case that requires a class component in modern React.'
  • 3.Always name what Error Boundaries do NOT catch — it shows you've used them in production: 'Error Boundaries only catch render-phase errors. Event handler errors need try/catch inside the handler, and async errors from fetch or setTimeout need a try/catch that then calls a function to trigger the boundary — or use react-error-boundary's useErrorBoundary hook.'
  • 4.Explain the two methods separately with different purposes: 'getDerivedStateFromError runs during the render phase — its only job is to return a state update that triggers the fallback render. componentDidCatch runs after commit — that's where you log because it's safe for side effects. They're two separate concerns: update UI vs notify monitoring service.'
  • 5.Mention granularity — it shows senior-level thinking: 'I place Error Boundaries at route level so page crashes are isolated, and at widget level for complex features like charts or third-party embeds that are more likely to throw. One root-level boundary is a safety net, not a strategy.'
  • 6.The react-error-boundary library is worth mentioning: 'Writing a class ErrorBoundary is boilerplate — in production I use the react-error-boundary library. It provides a declarative component with a fallbackRender prop and an onReset callback, plus a useErrorBoundary hook that lets functional components throw errors into the nearest boundary programmatically.'

Practice Questions

No questions tagged to this topic yet.

Related Topics

React Code Splitting & Lazy Loading — Complete Interview Guide
Intermediate·6–10 Qs
React Component Lifecycle — Complete Interview Guide
Intermediate·10–15 Qs
Concurrent Rendering (React 18) Interview Questions
Advanced·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz