How to Implement Error Boundaries in React

This article provides a practical guide on how to implement Error Boundaries in React to prevent application crashes caused by JavaScript errors. You will learn how to create a custom Error Boundary component using lifecycle methods, how to wrap your components to catch rendering errors, and what the limitations of this approach are in a React application.

What is an Error Boundary?

An Error Boundary is a React component that catches JavaScript errors anywhere in its child component tree, logs those errors, and displays a fallback UI instead of crashing the entire application. By default, if a rendering error occurs in React, the entire component tree is unmounted, resulting in a blank screen for the user. Error Boundaries act like a try-catch block but for React components.

Creating an Error Boundary Component

Currently, React requires Error Boundaries to be written as class components, as functional components do not yet support the necessary lifecycle methods (getDerivedStateFromError and componentDidCatch).

Here is a standard implementation of an Error Boundary component:

import React, { Component } from 'react';

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

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

  // Log the error to an error reporting service
  componentDidCatch(error, errorInfo) {
    console.error("Error caught by boundary:", error, errorInfo);
    // Example: sendFeedbackToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return this.props.fallback || <h1>Something went wrong. Please try again.</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Key Lifecycle Methods Explained:

How to Use the Error Boundary

To use your Error Boundary, import it and wrap it around the components you want to protect. You can wrap your entire application, or scope it to specific features like dashboards, sidebars, or interactive widgets.

import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import MyWidget from './MyWidget';

function App() {
  return (
    <div className="App">
      <header>My Application Header</header>
      
      {/* Scope the error boundary so a widget crash doesn't break the header */}
      <ErrorBoundary fallback={<div>Failed to load this widget.</div>}>
        <MyWidget />
      </ErrorBoundary>
    </div>
  );
}

export default App;

Using the react-error-boundary Library

If you prefer to avoid writing class components, you can use the popular community package react-error-boundary, which provides a functional wrapper and hooks.

First, install the library:

npm install react-error-boundary

Then, implement it in your functional component:

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

function FallbackComponent({ error, resetErrorBoundary }) {
  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={FallbackComponent}
      onReset={() => {
        // Reset the state of your app here to try and recover
      }}
    >
      <MyWidget />
    </ErrorBoundary>
  );
}

Limitations of Error Boundaries

Error Boundaries do not catch errors for the following scenarios:

  1. Event Handlers: Errors inside event handlers (like click listeners) must be handled with regular JavaScript try-catch blocks.
  2. Asynchronous Code: Errors in setTimeout, requestAnimationFrame, or fetch requests.
  3. Server-Side Rendering (SSR): Errors thrown during SSR (though they will catch them once the application hydates on the client side).
  4. Errors in the Boundary Itself: An Error Boundary cannot catch errors thrown inside its own code; it only catches errors in its children.