React Error Boundaries

Catching and handling errors in React components

🛡️ What are Error Boundaries?

Error Boundaries are React components that catch JavaScript errors in their child component tree, log errors, and display a fallback UI instead of crashing the entire application.


import { Component } from 'react';

class ErrorBoundary extends Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}
                                    

Error Boundary Concepts

🎯

Error Catching

Catches errors in child components

componentDidCatch(error, info)
🔄

Fallback UI

Shows alternative UI when error occurs

return <ErrorMessage />;
📝

Error Logging

Log errors for debugging

console.error(error, errorInfo);
🌳

Component Tree

Protects specific parts of app

<ErrorBoundary>
  <App />
</ErrorBoundary>

🔹 Basic Error Boundary

Create a simple error boundary class component. Error boundaries must be class components because they use lifecycle methods not available in function components.

import { Component } from 'react';

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

  static getDerivedStateFromError(error) {
    // Update state so next render shows fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Log error to error reporting service
    console.error('Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '20px', textAlign: 'center' }}>
          <h1>😕 Oops! Something went wrong.</h1>
          <p>We're sorry for the inconvenience.</p>
        </div>
      );
    }

    return this.props.children;
  }
}

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

Output (When Error Occurs):

😕 Oops! Something went wrong.

We're sorry for the inconvenience.

🔹 Error Boundary with Reset

Add a reset button to recover from errors. This allows users to try again without refreshing the entire page.

import { Component } from 'react';

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

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

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

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

  render() {
    if (this.state.hasError) {
      return (
        <div style={{ padding: '20px', textAlign: 'center' }}>
          <h1>⚠️ Something went wrong</h1>
          <p>{this.state.error?.message}</p>
          <button
            onClick={this.resetError}
            style={{
              padding: '10px 20px',
              background: '#007cba',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            Try Again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Output (With Reset Button):

⚠️ Something went wrong

Cannot read property 'name' of undefined

🔹 Multiple Error Boundaries

Use multiple error boundaries to isolate errors to specific parts of your app. This prevents one broken component from crashing the entire application.

function App() {
  return (
    <div>
      <Header />
      
      <ErrorBoundary>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary>
        <MainContent />
      </ErrorBoundary>
      
      <ErrorBoundary>
        <Footer />
      </ErrorBoundary>
    </div>
  );
}

// If MainContent crashes, Sidebar and Footer still work!

🔹 Custom Error Boundary with Logging

Create an error boundary that sends errors to a logging service. This helps you track and fix production errors.

import { Component } from 'react';

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

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

  componentDidCatch(error, errorInfo) {
    // Send to error tracking service
    this.logErrorToService(error, errorInfo);
  }

  logErrorToService = (error, errorInfo) => {
    // Example: Send to your logging service
    fetch('/api/log-error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        error: error.toString(),
        errorInfo: errorInfo.componentStack,
        timestamp: new Date().toISOString()
      })
    });
  };

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <h1>Error occurred</h1>;
    }
    return this.props.children;
  }
}

// Usage with custom fallback
function App() {
  return (
    <ErrorBoundary fallback={<CustomErrorPage />}>
      <MyApp />
    </ErrorBoundary>
  );
}

🔹 What Error Boundaries Catch

Error boundaries have limitations on what they can catch:

✅ Error Boundaries CATCH:

  • Rendering errors: Errors during component render
  • Lifecycle methods: Errors in lifecycle methods
  • Constructor errors: Errors in component constructors
  • Child component errors: Errors in the component tree below

❌ Error Boundaries DON'T CATCH:

  • Event handlers: Use try-catch in event handlers
  • Async code: setTimeout, promises, async/await
  • Server-side rendering: SSR errors
  • Errors in error boundary itself: Can't catch its own errors

🔹 Handling Event Handler Errors

Since error boundaries don't catch event handler errors, use try-catch blocks:

function MyComponent() {
  const [error, setError] = useState(null);

  const handleClick = () => {
    try {
      // Code that might throw an error
      riskyOperation();
    } catch (error) {
      // Handle error manually
      setError(error.message);
      console.error('Error in event handler:', error);
    }
  };

  if (error) {
    return <div>Error: {error}</div>;
  }

  return <button onClick={handleClick}>Click Me</button>;
}

// For async operations
async function handleSubmit() {
  try {
    await fetchData();
  } catch (error) {
    setError(error.message);
  }
}

🔹 Error Boundary Best Practices

Follow these guidelines for effective error handling:

// ✅ Good: Wrap at strategic points
<ErrorBoundary>
  <Routes />
</ErrorBoundary>

// ✅ Good: Multiple boundaries for isolation
<ErrorBoundary><Sidebar /></ErrorBoundary>
<ErrorBoundary><Content /></ErrorBoundary>

// ✅ Good: Custom fallback UI
<ErrorBoundary fallback={<ErrorPage />}>

// ✅ Good: Log errors to service
componentDidCatch(error, info) {
  logToService(error, info);
}

// ❌ Bad: Wrapping every component
<ErrorBoundary><Button /></ErrorBoundary>

// ❌ Bad: No error logging
componentDidCatch() { /* empty */ }

// ❌ Bad: Generic error message
return <div>Error</div>;

🧠 Test Your Knowledge

What type of component must Error Boundaries be?