Skip to main content
Technology & EngineeringReact Patterns198 lines

Error Boundaries

Error boundary pattern for gracefully catching and recovering from runtime errors in React component trees

Quick Summary18 lines
You are an expert in the Error Boundary pattern for building React applications.

## Key Points

- **Error Catching Scope**: An error boundary catches errors thrown during render and in lifecycle methods of its entire subtree.
- **Fallback UI**: When an error is caught, the boundary renders a fallback instead of the broken tree.
- **Recovery**: Error boundaries can offer retry mechanisms that reset their internal error state and attempt to re-render the children.
- **Limitations**: Error boundaries do not catch errors in event handlers, asynchronous code (setTimeout, fetch), or server-side rendering. Those require try-catch or `.catch()`.
- Place error boundaries at meaningful UI boundaries — page level, widget level, or feature section level.
- Always pair Suspense boundaries with an outer Error Boundary to handle rejected promises.
- Log caught errors to a monitoring service (Sentry, Datadog, etc.) in `componentDidCatch` or `onError`.
- Provide actionable fallback UIs with retry buttons, not just "something went wrong."
- Use `resetKeys` in `react-error-boundary` to auto-reset the boundary when relevant props change (e.g., after navigation).
- **One giant boundary at the root**: A single top-level boundary replaces the entire UI on any error. Use multiple granular boundaries so only the broken section fails.
- **Catching event handler errors**: Error boundaries do not catch errors in `onClick`, `onChange`, etc. Use local try-catch in event handlers.
- **Not resetting state**: After an error, re-rendering the same broken component without resetting its state will throw again. Provide a reset mechanism.
skilldb get react-patterns-skills/Error BoundariesFull skill: 198 lines
Paste into your CLAUDE.md or agent config

Error Boundaries — React Patterns

You are an expert in the Error Boundary pattern for building React applications.

Overview

Error boundaries are React components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors. They prevent a single broken component from crashing the entire application by rendering a fallback UI instead. Error boundaries are class components that implement componentDidCatch and/or static getDerivedStateFromError. In modern React, they are often wrapped in a reusable library component like react-error-boundary.

Core Philosophy

Error boundaries embody a fundamental principle of resilient systems: failures should be isolated, not propagated. A single broken widget on a dashboard should not bring down the navigation, the sidebar, and every other widget on the page. Without error boundaries, an unhandled throw during render crashes the entire React tree, leaving users staring at a blank screen. Error boundaries draw fault lines through your UI so that failures are contained to the smallest meaningful section.

The philosophy extends beyond crash prevention to user experience design. A well-placed error boundary does not just catch errors — it communicates what went wrong and offers a path forward. A retry button, a link to refresh, or a message explaining which section failed gives users agency. Silent failures and generic "something went wrong" messages are almost as bad as a full crash because they leave users without recourse.

Thinking in error boundaries also forces you to think about your component tree's architecture. Where you place boundaries reveals which parts of your UI are independent and which are tightly coupled. If you cannot place a boundary around a section without breaking the page, that section is too entangled with its surroundings. Error boundaries are both a safety mechanism and an architectural litmus test.

Core Concepts

  • Error Catching Scope: An error boundary catches errors thrown during render and in lifecycle methods of its entire subtree.
  • Fallback UI: When an error is caught, the boundary renders a fallback instead of the broken tree.
  • Recovery: Error boundaries can offer retry mechanisms that reset their internal error state and attempt to re-render the children.
  • Limitations: Error boundaries do not catch errors in event handlers, asynchronous code (setTimeout, fetch), or server-side rendering. Those require try-catch or .catch().

Implementation Patterns

Class-Based Error Boundary

import { Component, ErrorInfo, ReactNode } from "react";

interface Props {
  fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
  onError?: (error: Error, info: ErrorInfo) => void;
  children: ReactNode;
}

interface State {
  error: Error | null;
}

class ErrorBoundary extends Component<Props, State> {
  state: State = { error: null };

  static getDerivedStateFromError(error: Error): State {
    return { error };
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    this.props.onError?.(error, info);
  }

  reset = () => {
    this.setState({ error: null });
  };

  render() {
    if (this.state.error) {
      const { fallback } = this.props;
      if (typeof fallback === "function") {
        return fallback(this.state.error, this.reset);
      }
      return fallback;
    }
    return this.props.children;
  }
}

export { ErrorBoundary };

Usage with Fallback UI

<ErrorBoundary
  fallback={(error, reset) => (
    <div role="alert">
      <h2>Something went wrong</h2>
      <pre>{error.message}</pre>
      <button onClick={reset}>Try again</button>
    </div>
  )}
  onError={(error, info) => {
    // Report to monitoring service
    reportError(error, info.componentStack);
  }}
>
  <Dashboard />
</ErrorBoundary>

Using react-error-boundary Library

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

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

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // Clear cached state, refetch, etc.
      }}
      resetKeys={[userId]}
    >
      <UserProfile userId={userId} />
    </ErrorBoundary>
  );
}

Granular Error Boundaries

function DashboardPage() {
  return (
    <div className="grid grid-cols-3 gap-4">
      <ErrorBoundary fallback={<WidgetError name="Revenue" />}>
        <RevenueWidget />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError name="Users" />}>
        <UsersWidget />
      </ErrorBoundary>
      <ErrorBoundary fallback={<WidgetError name="Orders" />}>
        <OrdersWidget />
      </ErrorBoundary>
    </div>
  );
}

function WidgetError({ name }: { name: string }) {
  return (
    <div className="p-4 bg-red-50 rounded">
      <p>{name} widget failed to load.</p>
    </div>
  );
}

Combining with Suspense

function DataSection() {
  return (
    <ErrorBoundary fallback={<ErrorMessage />}>
      <Suspense fallback={<Skeleton />}>
        <AsyncDataComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

Best Practices

  • Place error boundaries at meaningful UI boundaries — page level, widget level, or feature section level.
  • Always pair Suspense boundaries with an outer Error Boundary to handle rejected promises.
  • Log caught errors to a monitoring service (Sentry, Datadog, etc.) in componentDidCatch or onError.
  • Provide actionable fallback UIs with retry buttons, not just "something went wrong."
  • Use resetKeys in react-error-boundary to auto-reset the boundary when relevant props change (e.g., after navigation).

Common Pitfalls

  • One giant boundary at the root: A single top-level boundary replaces the entire UI on any error. Use multiple granular boundaries so only the broken section fails.
  • Catching event handler errors: Error boundaries do not catch errors in onClick, onChange, etc. Use local try-catch in event handlers.
  • Not resetting state: After an error, re-rendering the same broken component without resetting its state will throw again. Provide a reset mechanism.
  • Silent failures: Catching errors without logging or reporting makes debugging impossible.
  • Forgetting async errors: Errors in useEffect callbacks, promises, and setTimeout are not caught by error boundaries. Handle these with .catch() and local error state.

Anti-Patterns

  • The single root boundary: Wrapping only the app root in one error boundary. Any error in any component replaces the entire UI with a fallback, which is functionally equivalent to a crash. Granular boundaries at the page, section, or widget level preserve the rest of the application.

  • Catch-and-ignore: Implementing componentDidCatch or onError without logging, reporting, or surfacing the error. This swallows failures silently, making production bugs invisible and impossible to diagnose.

  • Retry without state reset: Offering a "Try again" button that re-renders the same broken component without clearing the state that caused the error. The component throws again immediately, creating an infinite error-reset loop.

  • Using error boundaries for expected states: Relying on error boundaries to handle empty data, loading failures from fetch, or validation errors. Error boundaries are for unexpected runtime crashes. Expected failure modes should be handled with conditional rendering, error state, and user-facing messages in the normal render path.

  • Duplicating boundary logic across the codebase: Writing a new class-based error boundary component every time one is needed instead of using a reusable boundary component or a library like react-error-boundary. This leads to inconsistent error handling, duplicated logging logic, and missed edge cases.

Install this skill directly: skilldb add react-patterns-skills

Get CLI access →