Skip to main content
Saved
Pattern
Difficulty Advanced

Error Boundary

Catch JavaScript errors in component trees and display fallback UI gracefully.

By Den Odell Added

Error Boundary

Problem

A simple typo in a sidebar widget, a malformed API response, or a third-party library throwing an unexpected exception can unmount your entire component tree and leave users staring at a blank screen.

I’ve seen users lose entire forms because an unrelated chart component failed to render. All their data and in-progress work vanished instantly. The browser console shows a helpful stack trace for developers, but users see nothing useful.

This happens because errors bubble up through your component tree until they hit the root, at which point everything crashes together. One busted footer widget shouldn’t destroy the entire page, but without proper error containment that’s exactly what happens.

Solution

Think of error boundaries as try-catch blocks for your component tree. You wrap sections of your UI in these special components that catch render errors and display fallback UI instead of crashing.

Everything outside the boundary keeps working. If your sales chart throws an error, users can still see the navigation and sidebar because you’ve contained the blast radius to just the chart area.

I recommend a layered approach: put an error boundary at the app root as a safety net, then add granular boundaries around risky features like dashboard widgets, third-party integrations, and user-generated content.

One caveat: error boundaries only catch errors during rendering, lifecycle methods, and constructors. They won’t catch event handlers, async code, or promise rejections. You still need regular try-catch for those.

Example

Here’s how to build error boundaries across different frameworks.

Basic Error Boundary

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

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

  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
  }

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

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h1>Something went wrong</h1>
          <button onClick={this.resetError}>Try again</button>
        </div>
      );
    }
    return this.props.children;
  }
}

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

Granular Boundaries

Wrapping individual widgets isolates failures so one broken component doesn’t take down the entire dashboard:

function Dashboard() {
  return (
    <ErrorBoundary fallback={<PageError />}>
      <ErrorBoundary fallback={<WidgetError />}>
        <SalesChart />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError />}>
        <RecentActivity />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError />}>
        <UserList />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

Custom Fallback UI

A render prop pattern allows tailored fallback UI with access to the error and reset function:

<ErrorBoundary
  fallback={(error, reset) => (
    <div>
      <h2>Widget failed to load</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  )}
>
  <ComplexWidget />
</ErrorBoundary>

Using react-error-boundary

The react-error-boundary library provides a functional API so you don’t need to write class components:

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

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

<ErrorBoundary FallbackComponent={ErrorFallback} onError={logErrorToService}>
  <Dashboard />
</ErrorBoundary>

Benefits

  • Your application stays partially functional when things break. Users can keep using navigation and unaffected features even when one component fails.
  • Users see helpful fallback UI instead of a blank screen; “This widget failed to load” is infinitely better than white nothingness.
  • You contain the blast radius so a broken chart doesn’t take down the entire dashboard.
  • Error logging becomes automatic when connected to Sentry or your monitoring tool, so you know about production errors immediately.
  • Retry buttons let users recover without refreshing the page, valuable for transient failures that just need a second attempt.
  • You can show friendly messages to users while logging detailed stack traces for developers.

Tradeoffs

  • Error boundaries only catch errors during rendering and lifecycle methods. Event handlers, async code, and promise rejections still need regular try-catch.
  • In React, error boundaries require class components, though react-error-boundary lets you avoid writing classes yourself.
  • Generic “Something went wrong” messages might hide bugs instead of fixing them; make sure you’re logging enough detail to actually debug issues.
  • Placement is tricky: too few boundaries means large sections crash together, too many means fallback UI everywhere.
  • Reset functionality seems simple but can be tricky. If you clear error state without fixing the cause, the same error might happen immediately.
  • Consider disabling error boundaries in development so errors bubble up visibly during testing.

Summary

Error boundaries catch JavaScript errors in component trees and display fallback UI instead of crashing the entire application. By placing boundaries strategically around risky code, you contain failures to small parts of the page while keeping the rest functional. This is essential for production applications where graceful degradation matters.

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.