Error Boundaries Pattern

Overview

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 crashing the whole application. Error boundaries only catch errors in the components below them in the tree and can't recover from their own errors.

Starting from React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree, so it's important to use error boundaries to provide a better user experience.

Example

Below is a component that will throw an error when its counter reaches 5. The error boundary catches the error and displays a fallback UI.

Error Boundary Example

This example demonstrates React Error Boundaries that catch errors in components.

Basic Error Boundary

Click the button until it reaches 5, and the error boundary will catch the error.

0

With Custom Fallback

This version uses a custom fallback UI and can reset itself.

0

Key points about Error Boundaries:

  • Catch errors in component trees
  • Display fallback UI instead of crashing
  • Can log errors centrally
  • Can recover from errors
  • Only work for component errors, not event handlers or async code

Code Implementation

// ErrorBoundary.tsx
import React, { Component, ErrorInfo, ReactNode } from "react";

interface ErrorBoundaryProps {
  children: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  // Called when an error is thrown in a child component
  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    // Update state to trigger fallback UI
    return { hasError: true, error };
  }

  // Log the error to an error reporting service
  componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
    console.error("Error caught by ErrorBoundary:", error, errorInfo);
    // You could also log to an error reporting service here
    // logErrorToService(error, errorInfo);
  }

  render(): ReactNode {
    if (this.state.hasError) {
      // Fallback UI
      return (
        <div className="p-4 bg-red-50 border border-red-200 rounded-lg">
          <h3 className="text-lg font-semibold text-red-700 mb-2">
            Something went wrong
          </h3>
          <p className="text-red-600 mb-4">
            {this.state.error?.message}
          </p>
          <button
            onClick={() => this.setState({ hasError: false, error: null })}
            className="px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700 text-sm"
          >
            Try Again
          </button>
        </div>
      );
    }

    // If no error, render children normally
    return this.props.children;
  }
}

// Usage:
// <ErrorBoundary>
//   <ComponentThatMightThrow />
// </ErrorBoundary>

Key Benefits

  • Prevent the entire application from crashing due to errors in components
  • Display meaningful error messages and fallback UI to users
  • Log errors for debugging and monitoring
  • Isolate errors to specific parts of the UI
  • Allow for recovery without full page reload
  • Create better user experience when things go wrong