Skip to content
Home JavaScript React Error Boundaries — The $120k Blank Dashboard

React Error Boundaries — The $120k Blank Dashboard

Where developers are forged. · Structured learning · Free forever.
📍 Part of: React.js → Topic 16 of 47
A single uncaught render error unmounts entire React tree — blank dashboard cost one team $120k.
🔥 Advanced — solid JavaScript foundation required
In this tutorial, you'll learn
A single uncaught render error unmounts entire React tree — blank dashboard cost one team $120k.
  • Error boundaries catch render-time errors only — use them to isolate crashing components.
  • Place boundaries strategically: global for safety, granular for resilience.
  • Always log errors via componentDidCatch to your monitoring service.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • Error boundaries catch render-time JavaScript errors in the React component tree
  • They work via getDerivedStateFromError (render fallback) and componentDidCatch (side effects)
  • Only class components can be error boundaries — hooks don't have the lifecycle methods
  • A single boundary can protect an entire subtree, but granular boundaries reduce user impact
  • Production gotcha: error boundaries catch render errors, not event handler or async code errors
  • Performance trade-off: wrapping every widget increases bundle size and maintenance overhead
🚨 START HERE

React Error Boundary Debug Cheat Sheet

Quick commands and checks to diagnose error boundary problems fast.
🟡

Blank screen, no error boundary fallback

Immediate ActionOpen browser console immediately
Commands
Look for uncaught errors in console — if none, React swallowed it silently
window.__REACT_DEVTOOLS_GLOBAL_HOOK__: check if React is mounted
Fix NowWrap your root component in a global error boundary with a simple fallback like <div>Something went wrong</div>
🟡

Error boundary fires but fallback is blank

Immediate ActionCheck the fallback component itself for errors
Commands
Render the fallback component directly outside the boundary to test
Add console.log inside componentDidCatch to verify error info
Fix NowReplace the fallback with a static JSX element (no state, no hooks) until the issue is understood
🟡

Only some users see the fallback

Immediate ActionCheck for browser-specific errors (e.g., IE11, Safari)
Commands
Enable full source maps in production build
Use error logging service to group errors by browser and component stack
Fix NowAdd a try-catch around the problematic third-party library call inside render, or wrap it in an isolated boundary
Production Incident

The Blank Dashboard That Cost $120k

A third-party analytics chart crashed on missing data — and nuked the entire dashboard in production.
SymptomUsers saw a completely white page when opening the dashboard. No error message, no fallback. The app was rendered unresponsive.
AssumptionThe team assumed that because the chart library was 'stable', it would never throw during render. They had no error boundary anywhere in the tree.
Root causeThe chart library attempted to access a property on undefined when the API returned an empty data array. React's default behaviour unmounted the entire component tree above the failing component.
FixWrap each widget (including the chart) in its own error boundary. Added a fallback UI that shows 'Widget unavailable' with a retry button. Deployed a regression test that checks for empty data.
Key Lesson
Every third-party component that renders user-controlled data needs its own error boundary.A single uncaught render error unmounts the whole tree — your fallback UI must be deliberate.Test with empty, null, and malformed data in component tests.
Production Debug Guide

Symptom → Immediate Action

Blank page with no console errorsCheck if error boundary exists at root. If yes, inspect the fallback UI — likely the boundary itself is broken.
Error boundary fallback shows but then crashes againWrap the boundary fallback in another boundary or ensure the fallback component doesn't throw. Use a static fallback with no state.
Partial UI disappears, rest worksLocate the affected subtree. The boundary that wraps it is working as expected. Look at the error logged via componentDidCatch to identify the culprit.
Error only occurs in production, not local devCheck minified stack traces. Use source maps. Enable error monitoring (Sentry/Datadog) to capture the original error and component stack.

Production React apps break. A payment widget fetches malformed JSON, a third-party chart library throws on null data, a lazy-loaded component fails to parse — and suddenly your user sees a completely blank page with no explanation. No error message, no fallback, just white. That is the default behaviour of an unhandled render error in React, and it is brutal. A blank screen is worse than a visible error because users assume the whole product is broken, not just one widget.

What Is React Error Boundaries?

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the crashed component tree. They work like a try-catch block for the render phase — but only for class components. The two key methods are getDerivedStateFromError (to update state and render a fallback) and componentDidCatch (to log side effects). Here's a minimal example:

ErrorBoundary.jsx · JSX
12345678910111213141516171819202122232425
import React from 'react';

export 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) {
    console.error('Error caught by boundary:', error, info);
    // Send to monitoring service like Sentry
    logErrorToService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <div>Something went wrong. Please try again.</div>;
    }
    return this.props.children;
  }
}
▶ Output
Usage: <ErrorBoundary><Widget /></ErrorBoundary>
🔥Forge Tip:
Write the fallback UI yourself — don't rely on a generic 'Something went wrong' message. Show a retry button, link to support, or a cached version of the content.
📊 Production Insight
Error boundaries only catch errors during render, lifecycle methods, and constructors.
They do NOT catch errors in event handlers, async code (setTimeout, Promises), or server-side rendering.
Rule: always wrap async operations in their own try-catch or redirect to a boundary reporting mechanism.
🎯 Key Takeaway
Implement error boundaries as class components with getDerivedStateFromError and componentDidCatch.
Place them strategically around failure-prone UI sections.
Remember that async code and event handlers are invisible to boundaries.

How Error Boundaries Work Internally

When a component throws during rendering, React walks up the component tree looking for the nearest parent error boundary. If it finds one, React calls getDerivedStateFromError with the thrown error, then re-renders the boundary with the updated state (hasError = true). The fallback UI is rendered instead of the crashed subtree. If no boundary is found, React unmounts the entire tree and logs the error to the console. This 'catch-and-render' mechanism only applies to errors thrown in the commit phase — not during reconciliation itself. React uses a special work loop that catches thrown values and checks for boundary components that implement either componentDidCatch or getDerivedStateFromError.

Mental Model
Mental Model: The Safety Net
Think of error boundaries as safety nets placed at different heights in a building.
  • Each net catches failures from the floors below it.
  • If the net is at the ground floor (root level), any fall from any floor brings down the whole building.
  • Nets on every floor (granular) limit damage to a single room.
  • The net itself must be sturdy — it can't be made of the same flimsy material as the floors it protects.
📊 Production Insight
React's error boundary traversal is O(depth of tree) — not a performance problem.
What matters is that getDerivedStateFromError runs during the render phase, so it must be a pure function with no side effects.
Use componentDidCatch for logging – it runs after the commit and has access to the full component stack.
🎯 Key Takeaway
Error boundaries work by bubbling errors up the tree until a boundary is found.
getDerivedStateFromError sets the fallback state; componentDidCatch handles side effects.
No boundary = entire tree unmounts.

Where to Place Error Boundaries in Your Component Tree

The placement of error boundaries is a strategic decision. You have two extremes: a single boundary at the root (global), which shows a full-page error for any crash, or granular boundaries around each widget. A common production pattern is a layered approach: - One boundary around the entire app to catch truly catastrophic failures. - Separate boundaries around each major section (header, sidebar, main content). - Additional boundaries around third-party widgets, charts, or data-heavy components. This lets the rest of the page remain interactive even if a widget fails. The trade-off is more code and potential memory overhead if you create too many boundary instances.

📊 Production Insight
Too many boundaries made it hard to trace the root cause of errors — you'd see a fallback but not know which widget broke.
We added a unique identifier to each boundary that logs along with the error info.
Rule: each boundary should have a human-readable name for easier debugging.
🎯 Key Takeaway
Don't wrap everything — wrap failure-prone components.
Use a layered approach: global + granular.
Name your boundaries to simplify log analysis.
Where to Place Your Next Error Boundary?
IfComponent renders data from an external API
UseWrap it individually — malformed responses are common
IfComponent uses a third-party UI library
UseWrap it — library updates can introduce rendering bugs
IfComponent is a static header or footer
UseNo boundary needed — they rarely throw and global boundary covers them
IfComponent is a lazy-loaded page route
UseWrap the lazy component — network failures can cause chunk load errors

What Error Boundaries Cannot Catch

This is the most common source of confusion. Error boundaries do NOT catch: - Errors inside event handlers (e.g., onClick, onChange) — use try-catch inside the handler. - Errors in asynchronous code (setTimeout, Promises, async/await) — wrap the async block or use a global promise rejection handler. - Errors thrown during server-side rendering (SSR) — boundaries only work in the client. - Errors in the boundary component itself — the boundary cannot catch its own errors. - Errors thrown during event bubbling or in context providers — those are outside the render phase. For these cases, you need a separate error handling strategy: try-catch in event handlers, window.onerror for unhandled client exceptions, and error boundaries only for rendering.

AsyncErrorFix.jsx · JSX
1234567891011
function ClickHandler() {
  const handleClick = async () => {
    try {
      await fetchData();
    } catch (error) {
      // Error boundaries can't catch this, handle it here
      showToast('Fetch failed');
    }
  };
  return <button onClick={handleClick}>Load Data</button>;
}
▶ Output
Always use try-catch in event handlers and async functions.
⚠ Common Pitfall: Async Errors
Don't assume your error boundary will catch a failed fetch inside a useEffect. That error occurs in the effect callback, which is not part of the render phase. You must catch it yourself.
📊 Production Insight
A production bug where a Promise rejection in a useEffect caused an uncaught error — but the component still rendered a partial UI.
The error boundary never fired because the rejection was outside the render phase.
We added a global unhandledrejection handler that logs to the monitoring system and shows a toast.
🎯 Key Takeaway
Error boundaries only cover render-time errors.
Use try-catch for event handlers, async functions, and effects.
Add global error handlers for the rest.

Testing Error Boundaries

You should test that your error boundary correctly renders the fallback when a child throws. Use Jest with React Testing Library. The key is to simulate a rendering error in a child component. Then assert that the fallback UI appears. Also test that the componentDidCatch is called with the correct error. Here's an example:

ErrorBoundary.test.jsx · JSX
123456789101112131415
import { render, screen } from '@testing-library/react';
import { ErrorBoundary } from './ErrorBoundary';

const ThrowComponent = () => {
  throw new Error('Test crash');
};

test('error boundary catches error and shows fallback', () => {
  render(
    <ErrorBoundary>
      <ThrowComponent />
    </ErrorBoundary>
  );
  expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
});
▶ Output
PASS: renders fallback UI when child throws
📊 Production Insight
We had a bug where the fallback itself threw because it tried to access a context provider that wasn't available after the crash.
We fixed it by making the fallback a simple static component with no dependencies.
Rule: never let the fallback rely on context or external state that may be corrupted.
🎯 Key Takeaway
Always test error boundaries with a throwing child.
Ensure the fallback is self-contained and doesn't depend on external state.
Also test that errors are logged to your monitoring service.

Production Patterns with Error Boundaries

In production apps, a single error boundary is rarely enough. Common patterns include: - Retry pattern: After showing the fallback, give the user a 'Retry' button that resets the boundary state by calling setState({ hasError: false }). - Logging pattern: In componentDidCatch, send the error to your monitoring service along with the component stack. - Bulkhead pattern: Wrap each micro-frontend or module in its own boundary so that a failure in one doesn't affect others. - Reset pattern: If the error was a transient network failure, you can clear the error boundary after a timeout and re-mount the children by forcing a key change on the boundary's container.

RetryBoundary.jsx · JSX
12345678910111213141516171819202122232425262728
import React from 'react';

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

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

  handleRetry = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      return (
        <div role="alert">
          <p>Something went wrong.</p>
          <button onClick={this.handleRetry}>Retry</button>
        </div>
      );
    }
    return this.props.children;
  }
}
▶ Output
Retry button resets boundary state, re-rendering children.
📊 Production Insight
Retry logic can cause infinite loops if the error is persistent — always add a max retry count.
We also used a fallback that shows cached data from localStorage if available.
Rule: never retry more than 3 times without alerting the user.
🎯 Key Takeaway
Add retry functionality with a cap (max 3 retries).
Log every error to your monitoring service.
Consider showing cached data in the fallback to preserve user experience.
🗂 Error Handling in React: Options Compared
When to use each mechanism
MechanismScopeCan Catch Render Errors?Can Catch Async Errors?Production Use Case
Error Boundary (class component)Child component treeYesNoWrap widgets, routes, third-party libraries
try-catch in event handlerThat handler onlyNoYes (if inside handler)Handle failed API calls, form submissions
window.onerror / global error handlerWhole pageYes (but no component stack)YesLast-resort logging, cannot show fallback UI
unhandledrejection eventPromise rejectionsNoYesCatch rejected Promises outside render

🎯 Key Takeaways

  • Error boundaries catch render-time errors only — use them to isolate crashing components.
  • Place boundaries strategically: global for safety, granular for resilience.
  • Always log errors via componentDidCatch to your monitoring service.
  • Add retry logic with a cap to give users a way to recover.
  • Test your boundaries with a throwing child and ensure the fallback is self-contained.

⚠ Common Mistakes to Avoid

    Placing a single error boundary at the root level
    Symptom

    Any widget crash shows a full-page error, making the entire app unusable instead of isolating the broken piece.

    Fix

    Use multiple granular boundaries around independent parts of the UI (sidebar, main content, header). Keep the root boundary as a last resort.

    Not logging errors in componentDidCatch
    Symptom

    Users report blank areas but you have no logs or stack traces to debug the cause.

    Fix

    Always call your error monitoring service inside componentDidCatch with the error and component stack.

    Forgetting that error boundaries don't catch async errors
    Symptom

    A useEffect that fetches data fails, but the error boundary never shows a fallback — the component renders with missing data.

    Fix

    Wrap the fetch call in a try-catch inside the effect, and use state to trigger the fallback manually.

    Using error boundaries to catch errors in event handlers
    Symptom

    An onClick handler throws, but the boundary doesn't catch it — the app crashes silently.

    Fix

    Use try-catch directly inside the event handler and show a toast or inline error.

Interview Questions on This Topic

  • QExplain how React error boundaries work and what they can and cannot catch.SeniorReveal
    Error boundaries are class components that implement either getDerivedStateFromError (to render a fallback) or componentDidCatch (to log side effects). They catch errors thrown during rendering, lifecycle methods, and constructors of the child tree. They do NOT catch errors in event handlers, async code (setTimeout, Promises, async/await), or during server-side rendering. They also cannot catch errors thrown in the boundary component itself. For async errors, you need try-catch inside the handler or a global rejection handler.
  • QHow would you test an error boundary component?Mid-levelReveal
    I would write a unit test using React Testing Library. Create a child component that throws an error during render. Render it inside the error boundary. Assert that the fallback UI appears. Also, mock a logging function and verify that componentDidCatch logs the error. Example: render(<ErrorBoundary><ThrowingComponent /></ErrorBoundary>) and expect(screen.getByText(/fallback/i)).toBeInTheDocument().
  • QWhat is the difference between getDerivedStateFromError and componentDidCatch?SeniorReveal
    getDerivedStateFromError is a static method called during the render phase. It receives the error and must return an object to update state (typically { hasError: true }). It must be pure with no side effects. componentDidCatch is called during the commit phase. It receives the error and an info object containing the component stack. You can perform side effects here like logging to a monitoring service. getDerivedStateFromError is for rendering the fallback; componentDidCatch is for logging and side effects.

Frequently Asked Questions

What is the simplest way to add an error boundary to an existing app?

Create a class component that implements getDerivedStateFromError and componentDidCatch. Wrap your existing component tree — start with a root-level boundary, then add granular boundaries around sections that use third-party libraries or external data.

Can I use hooks to create an error boundary?

No — error boundaries require the class component lifecycle methods getDerivedStateFromError and componentDidCatch. Hooks do not have equivalents. However, you can create a reusable error boundary component once and use it throughout your app.

How do I handle errors in event handlers that fire before the component renders?

Use try-catch inside the event handler function. Show a user-friendly error message or toast. If the error is severe, you can programmatically set state to trigger an error boundary by calling a function that throws during the next render, but that's not recommended.

Does the order of multiple error boundaries matter?

Yes — the closest boundary to the crashing component catches the error. If you have a child boundary inside a parent, the child catches first. This allows you to have a granular fallback for a widget and a more generic fallback for the whole page if the widget's boundary fails.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousReact Lifecycle MethodsNext →React Suspense and Lazy Loading
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged