React error handling with react-error-boundary

When building applications, errors are inevitable. Even with the best code practices, unexpected errors can occur at runtime, perhaps causing the application to crash completely and leaving users frustrated.That’s why, how we handle the errors is really crucial.

React error boundaries come to the rescue by allowing developers to catch and handle errors, preventing the app from breaking entirely and ensuring a smoother user experience.

React Error Boundaries

React error boundaries provide a way to gracefully handle runtime errors and prevent them from crashing the entire application. They allow developers to catch and handle errors within specific components, providing a fallback UI instead of leaving the user facing a blank screen or a broken UI.

Here’s how we handle the errors using React Error Boundaries.

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

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

  componentDidCatch(error, info) {
    logErrorToMyService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // we can render any custom fallback UI
      return this.props.fallback;
    }

    return this.props.children;
  }
}

Then we can wrap the component inside ErrorBoundary.

<ErrorBoundary fallback={<p>Error occurred.</p>}>
  <MyComponent />
</ErrorBoundary>

React Error boundaries is a great way to provide a seamless user experience. However, there are some limitations to it.

Limitations of React Error Boundaries:

1. Errors in Event Handlers and Asynchronous Code:

React error boundaries can only catch errors that occur during the rendering phase. They do not capture errors within event handlers, asynchronous code (e.g., setTimeout, fetch), or during server-side rendering.

Example:

class MyComponent extends React.Component {
  handleClick() {
    // This error will not be caught by the error boundary
    throw new Error('Error in event handler');
  }

  render() {
    return <button onClick={handleClick}>Click me</button>;
  }
}

2. Errors in Render Methods of Children:

React error boundaries cannot catch errors that occur within the render methods of their child components. Each component should have its error boundary to handle errors in its own rendering tree.

Example:

class ParentComponent extends React.Component {
  render() {
    // Error in ChildComponent's render method will not be caught by this error boundary
    return <ChildComponent />;
  }
}

This is where the react-error-boundary package comes into play. It offers enhanced error handling features and a more flexible approach to dealing with errors in React applications, enabling developers to create more robust and user-friendly error handling mechanisms.

react-error-boundary

The react-error-boundary package provides various props that offer customization and control over the behavior of error boundaries. Let’s explore some of the key props and their usage:

1. FallbackComponent: The FallbackComponent prop allows us to specify a custom component to be rendered when an error occurs within the error boundary. It gives us the flexibility to create a visually appealing and informative UI to display the error and provide any necessary actions.

2. fallbackRender: Similar to FallbackComponent, the fallbackRender prop lets us define a custom render function for rendering the error fallback UI. It provides more control over the rendering process and allows for more advanced error handling logic.

3. onError: The onError prop takes a callback function that gets called with the error and the component stack trace when an error is caught by the error boundary. It enables us to perform additional actions such as logging the error or sending error reports to external services.

4. onReset: The onReset prop allows us to provide a callback function that is triggered when the error boundary successfully resets after an error. It can be useful for performing cleanup actions or updating the component’s state after error recovery.

5. fallbackProps: The fallbackProps prop allows us to pass additional props to the FallbackComponent or the fallbackRender function. It can be useful for providing context or additional data to the error fallback UI.

6. retry: The retry prop is a boolean value that determines whether the error boundary should allow retrying the operation that caused the error. When set to true, the resetErrorBoundary function can be called from the error fallback UI to retry the operation.

import React, { useState } from "react";
import { ErrorBoundary } from "react-error-boundary";

const App = () => {
  const ErrorFallback = ({ error }) => {
    // we can customize the UI as we want
    return (
      <div
        style="color:red"
      >
        <h2>
          Oops! An error occurred
          <br />
          <br />
          {error.message}
        </h2>
        {/* Additional custom error handling */}
      </div>
    );
  };

  const logError = (error) => {
    setErrorMessage(error.message);
    console.error(error);
    // we can also send the error to a logging service
  };

  const handleResetError = () => {
    console.log("Error boundary reset");
    setErrorMessage("");
    //additional logic to perform code cleanup and state update actions
  };

  const UserProfile = ({ user }) => {
    return (
      <div>
        <h2>User Profile</h2>
        <p>Name: {user.name}</p>
        <p>Email: {user.personalID.email}</p>
      </div>
    );
  };

  const user = {
    name: 'Shruti Apte',
    //missing personalID.email property on purpose
  };

  const [errorMessage, setErrorMessage] = useState("");

  return (
    <ErrorBoundary
      onError={logError}
      onReset={handleResetError}
      FallbackComponent={ErrorFallback}
    >
      <UserProfile user={user} />
    </ErrorBoundary>
  );
};

export default App;

Below is the UI behavior after the react-error-boundary catches an error.

We can also use fallbackRender prop instead FallbackComponent. We can simply pass the inline function as a prop.

    <ErrorBoundary
      fallbackRender={({ error, resetErrorBoundary }) => (
        <div>
          <h2>An error occurred: {error.message}</h2>
          <button onClick={resetErrorBoundary}>Retry</button>
        </div>
      )}
    >
      {/* Component code */}
    </ErrorBoundary>

The choice between these 2 props totally depends on our preference and the complexity of the error fallback UI. If we prefer creating a separate component with its own rendering logic, FallbackComponent is a suitable choice. On the other hand, if we need more control and flexibility in rendering the error UI, fallbackRender allows us to define a custom function inline.

useErrorBoundary hook

The react-error-boundary package also provides a useErrorBoundary hook, which allows us to create error boundaries using a more concise and functional approach.

Let’s start by importing the hook

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

Now we can modify the ErrorFallback component in above example to see how this hook works.

const ErrorFallback = ({ error}) => {

const { resetErrorBoundary } = useErrorBoundary();

// we can customize the UI as we want
  return (
    <div>
      <h2 style="color: red">Oops! An error occurred: {error.message}</h2>
      <button onClick={resetErrorBoundary}>Retry</button>
    </div>
  );
};

The resetErrorBoundary function allows the user to reset the error boundary and attempt to render the UserProfile component again. We can also pass the this function to onReset prop to reset the component.

withErrorBoundary HOC

react-error-boundary package can also be used as a higher-order component that accepts all of the same props as above:

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

Let’s wrap UserProfile component inside withErrorBoundary HOC.

const App = () => {
  const user = {
    name: 'Shruti Apte',
    // Missing email property intentionally to trigger an error
  };

  const UserProfileWithBoundary = withErrorBoundary(UserProfile, {
  FallbackComponent: ErrorFallback,
});

  return (
    <UserProfileWithBoundary user={user} />
    //we can also render other components wrapped inside withErrorBoundary HOC. 
  );
};

export default App;

Using the withErrorBoundary HOC, we can easily apply error boundaries to multiple components without having to repeat the error handling code in each component.

Conclusion:

React error boundaries are a valuable tool for handling errors within specific components. However, they have limitations when it comes to capturing errors in event handlers, asynchronous code, and child component render methods. The react-error-boundary package provides additional features and flexibility to overcome these limitations, such as custom error fallback components and retry functionality. By using the react-error-boundary package, developers can enhance error handling in React applications and provide a better user experience in the face of runtime errors.

Need help on your Ruby on Rails or React project?

Join Our Newsletter