Skip to content
Production Ready — Part 5 of 30

React Error Boundaries: Don't Let One Bug Crash Everything

Written by claude-sonnet-4 · Edited by claude-sonnet-4
reacterror-boundariesproductionerror-handlingreact-19react-error-boundarydebuggingresilience

Production Ready — Part 5 of 30


The 3 AM Wake-Up Call

It was a Friday evening deploy. The team shipped a new analytics widget on the main dashboard. Everything looked fine in staging. Then, around midnight, Slack lit up: users were seeing a completely blank screen — not just the analytics panel, but the entire app.

The culprit? One line deep inside the new widget was calling .toFixed(2) on a value that occasionally came back as null from the API. That threw a TypeError. React caught the error during rendering, had no idea what to do with it, and unmounted the entire component tree.

Blank screen. Zero UI. Every user logged out and staring at nothing.

This is not a hypothetical. A July 2025 Stack Overflow thread documented exactly this pattern: a corrupted local cache caused LastSeenDateComponent to throw an Uncaught RangeError: Invalid time value, bringing down the whole app. The developer's shock — "it should only affect the one component" — is something I hear constantly from vibe coders. This is where Error Boundaries come in.


What React Does Without Error Boundaries

Here's the brutal truth baked into React since version 16: any uncaught render error unmounts your entire component tree.

The React team made this intentional call. Their reasoning: a corrupted, half-rendered UI (like a payments screen showing a wrong amount) is worse than a blank screen. But "blank screen" is not the same as "good UX." It's just the lesser of two disasters — unless you take control.

Without protection, one bad prop, one undefined where you expected a string, one third-party component with a bug, and your users are staring at nothing.


Error Boundaries: The try/catch of Your Component Tree

An Error Boundary is a React class component that wraps part of your UI. If anything inside it throws during rendering, React calls the boundary instead of unmounting the whole tree. The boundary renders a fallback UI. Everything outside the boundary keeps working.

Here's the minimal implementation:

import React from 'react';

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

  static getDerivedStateFromError(error) {
    // Flip the flag so we render the fallback on next render
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // This is your moment to log to Sentry, Datadog, or your own endpoint
    console.error('Boundary caught:', error, errorInfo.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <div role="alert">Something went wrong. Try refreshing.</div>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Usage is a one-liner wrap:

<ErrorBoundary>
  <AnalyticsWidget />
</ErrorBoundary>

Now when AnalyticsWidget throws, the boundary shows the fallback. Your sidebar, navbar, and the rest of the dashboard? Still running.


What Error Boundaries Do NOT Catch

This is where most vibe coders get tripped up. Error Boundaries only catch errors thrown during rendering and in lifecycle methods. They will not catch:

  • Event handlers — use try/catch inside onClick/onChange
  • Async codefetch, setTimeout, promises
  • Server-side rendering errors
  • Errors thrown inside the boundary itself

If you do await fetch('/api/data') in a useEffect and it throws, the boundary will not catch that. You need explicit try/catch in your effect or a library that bridges async errors to the boundary.


The Right Tool: react-error-boundary

Writing class components in 2025 feels like putting on a tuxedo to check your mail. The react-error-boundary library by Brian Vaughn gives you a functional API that covers the same ground — and handles async errors through the useErrorBoundary hook.

Install it:

npm install react-error-boundary

As of v6.0.0 (released May 2025), the package is ESM-only to align with modern tooling. If you're still on CommonJS builds, you'll need to handle this in your bundler config.

Here's the idiomatic usage with a recovery button:

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

function WidgetFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert" className="error-fallback">
      <p>Failed to load widget: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function Dashboard() {
  return (
    <div className="dashboard">
      <Sidebar />
      <ErrorBoundary
        FallbackComponent={WidgetFallback}
        onError={(error, info) => logToSentry(error, info)}
        onReset={() => queryClient.invalidateQueries(['analytics'])}
      >
        <AnalyticsWidget />
      </ErrorBoundary>
    </div>
  );
}

The onReset prop is powerful — wire it to invalidate your query cache, reset local state, or anything else needed to recover cleanly.

For async errors in event handlers, use useErrorBoundary:

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

function SaveButton() {
  const { showBoundary } = useErrorBoundary();

  const handleSave = async () => {
    try {
      await saveData();
    } catch (error) {
      showBoundary(error); // Escalates to the nearest ErrorBoundary
    }
  };

  return <button onClick={handleSave}>Save</button>;
}

React 19: Error Boundaries Get a Upgrade

In December 2024, React 19 shipped with a significant improvement to error handling. Previously, a single caught error would log three separate entries to the console — a maddening experience when debugging production issues.

React 19 now logs a single consolidated error with all the relevant context. It also introduced two new root-level hooks:

  • onCaughtError — fires when React catches an error in an Error Boundary
  • onUncaughtError — fires when an error throws and no boundary catches it

Here's how to wire both up with Sentry:

import * as Sentry from '@sentry/react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'), {
  onUncaughtError: Sentry.reactErrorHandler(),
  onCaughtError: Sentry.reactErrorHandler(),
  onRecoverableError: Sentry.reactErrorHandler(),
});

root.render(<App />);

The distinction matters: onCaughtError tells you about errors your boundaries handled gracefully — these are known failure zones. onUncaughtError tells you about catastrophic failures that no boundary stopped — these need immediate attention.

In React 19+, the Sentry docs recommend treating these as complementary layers: reactErrorHandler for global telemetry, <ErrorBoundary> components for scoped fallback UI.


Where to Place Your Boundaries

This is the strategic decision that separates production-grade apps from fragile ones. Think in three layers:

1. Route level — Wrap each page/route. A broken settings page shouldn't kill the billing page.

<Routes>
  <Route path="/dashboard" element={
    <ErrorBoundary FallbackComponent={PageError}>
      <DashboardPage />
    </ErrorBoundary>
  } />
  <Route path="/settings" element={
    <ErrorBoundary FallbackComponent={PageError}>
      <SettingsPage />
    </ErrorBoundary>
  } />
</Routes>

2. Feature level — Wrap independent widgets, data panels, and third-party embeds individually. This is exactly what Facebook Messenger does — sidebar, info panel, conversation log, and message input each have their own boundary.

3. Third-party components — Always wrap these. You have no control over their code. A map component, a chart library, a rich text editor — any of these can throw on bad data.


The Production Checklist

Before your next deploy, verify every item:

  • Route-level boundaries — Every top-level route is wrapped with an ErrorBoundary
  • Widget-level boundaries — Each independently-failable component (charts, feeds, maps) has its own boundary
  • Third-party wraps — Every external component library is wrapped
  • onError logging — All boundaries call a real error reporting service (Sentry, Datadog, etc.) in componentDidCatch or the onError prop
  • Meaningful fallback UI — Fallbacks show actionable messages, not just "Something went wrong"
  • Reset capability — Use onReset to clear stale state so "Try again" actually works
  • Async error escalation — Event handlers and effects that can fail use useErrorBoundary or showBoundary
  • React 19 root hooks — If on React 19+, wire onUncaughtError and onCaughtError to your monitoring stack
  • ESM compatibility — If using react-error-boundary v6, verify your bundler handles ESM-only packages
  • Tested under failure — Deliberately throw errors in dev to confirm fallback UI renders as expected

Ask The Guild

This week's community prompt:

What's the most unexpected component crash you've seen take down a production React app? Was it a third-party library, bad API data, or something else entirely? How did you scope your error boundaries after the incident?

Share your war story in the guild — the best ones become case studies for future articles.


Tom Hundley is a software architect with 25 years of experience. He writes the Production Ready series for the AI Coding Guild.

Copy A Prompt Next

Review and debug

If this article changed how you think about the problem, copy a prompt that turns that judgment into one safe, reviewable next step.

Matching public prompts

23

Keep the task scoped, copy the prompt, then inspect one reviewable diff before the agent continues.

Need the safest first move instead? Open the curated sample prompts before you browse the broader library.

DebuggingWorking With AI Tools

Debug This Without Thrashing

Use this when the app is already broken and you need the agent to isolate one likely cause, propose a narrow fix, and define how to verify it.

Preview
"Help me debug this issue systematically.
Feature: [what is broken]
Error or symptom: [full message or precise symptom]
Expected behavior: [what should happen]
Actual behavior: [what happens instead]
Production Ready

Use this production insight inside a full build sequence

Production articles show you what breaks in the real world. The right path turns that lesson into a sequence you can ship with instead of just nodding at.

Best Next Path

Building a Real Product

Guild Member · $29/mo

Bridge demos to software people can trust: auth, billing, email, analytics, and the surrounding product plumbing.

20 lessonsIncluded with the full Guild Member library

Need the free route first?

Start with Start Here — Build Safely With AI if you want the workflow and vocabulary before you dive into the deeper path above.

T

About Tom Hundley

Tom Hundley writes for builders who need stronger technical judgment around AI-assisted software work. The Guild turns production experience into public articles, copy-paste prompts, and structured learning paths that help non-software developers supervise AI agents more safely.

Do this next

Leave this article with one concrete move. Copy the matching prompt, or start with the path that teaches the safest next skill in sequence.