Yusuff Faruq Frontend web developer and anime lover from Nigeria.

React error handling with react-error-boundary

5 min read 1401

react-error-boundary Handling React

​​Editor’s note: This article was updated on 13 April 2022 to reflect the most recent information for react-error-boundary, including use of the withErrorBoundary function as a higher order component.

Errors are bound to happen in our applications, whether they’re server-related errors, edge cases, or others. As such, many methods have been developed to prevent these errors from interfering with user and developer experience. In React, one such method is the use of error boundaries.

In this article, we’ll examine React error handling using react-error-boundary. We’ll cover the following:

Error boundaries in React

Error boundaries were introduced in React 16 as a way to catch and handle JavaScript errors that occur in the UI parts of our component. So error boundaries only catch errors that occur in a lifecycle method, render method, and inside Hooks like useEffect. According to the React documentation, error boundaries do not handle errors in:

  • Event handlers
  • Asynchronous code (e.g., setTimeout or requestAnimationFrame callbacks)
  • Server-side rendering
  • Errors thrown in the error boundary itself (rather than its children)

So basically, error boundaries only handle errors in the parts of our code that involve React.

To create an error boundary, we simply have to create a class component and define a state variable for determining whether the error boundary has caught an error. Our class component should also have at least three methods:

  1. A static method called getDerivedStateFromError, which is used to update the error boundary’s state
  2. A componentDidCatch lifecycle method for performing operations when our error boundaries catch an error, such as logging to an error logging service
  3. A render method for rendering our error boundary’s child or the fallback UI in case of an error

Here’s an example (taken from the React docs) of what our simple error boundary should look like:

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, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

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

    return this.props.children; 
  }
}

react-error-boundary

react-error-boundary is a wrapper around React’s error boundary that allows developers to implement error boundaries in code without building them from scratch. With react-error-boundary, we can simply wrap components where we expect errors with the provided ErrorBoundary component and pass in some extra props to customize our error boundary’s behavior.

In this article, I will work through using react-error-boundary to deal with errors in a React application. Let’s take a look at what the library offers.

ErrorBoundary component

The ErrorBoundary component is the main component available in react-error-boundary. It allows us to implement the typical React error boundary with less code.

Here’s a very basic use case of ErrorBoundary:

function App(){
  ...
  return (
    <ErrorBoundary
          FallbackComponent={OurFallbackComponent}
        >
          <ComponentThatMightThrowAnError/>
    </ErrorBoundary>
  );
}

const OurFallbackComponent = ({ error, componentStack, resetErrorBoundary }) => {
  return (
    <div>
      <h1>An error occurred: {error.message}</h1>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
};

In the above component, we simply wrap our component with the ErrorBoundary component and pass in our fallback component to the FallbackComponent prop so that if there’s an error (that can be caught and handled by react-error-boundary), our fallback component will be rendered; otherwise, our component will be rendered.

We also have the fallbackRender prop, which is a render prop-based API for specifying our fallback component in an inline manner. Here’s the above code block using the fallbackRender prop:

function App(){
  ...
  return (
    <ErrorBoundary
      fallbackRender =  {({error, resetErrorBoundary, componentStack}) => (
          <div>
          <h1>An error occurred: {error.message}</h1>
          <button onClick={resetErrorBoundary}>Try again</button>
        </div>
      )}
    >
      <ComponentThatMightThrowAnError/>
    </ErrorBoundary>
  );
}

The ErrorBoundary also has an onError prop, which acts as a listener that is triggered when our error boundary catches and handles an error in its child components. It is from this place that we might choose to log such errors to whatever error logging service we might be using.

function App(){
  ...

  return (
    <ErrorBoundary
      onError = {(error, componentStack) => {
        logToErrorLoggingService(error, componentStack);
      }}
      ...
    >
      <ComponentThatMightThrowAnError/>
    </ErrorBoundary>
  );
}

Resetting error boundaries

react-error-boundary also provides a way for our code to recover from errors caught by our error boundaries. This is done using the reset keys and the resetErrorBoundary function passed to the fallback component.

The best way to explain how this works is to use an example code block taken directly from the React documentation:

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

function Bomb() {
  throw new Error('💥 KABOOM 💥')
}

function App() {
  const [explode, setExplode] = React.useState(false)
  return (
    <div>
      <button onClick={() => setExplode(e => !e)}>toggle explode</button>
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onReset={() => setExplode(false)}
        resetKeys={[explode]}
      >
        {explode ? <Bomb /> : null}
      </ErrorBoundary>
    </div>
  )
}

As we can see from the code above, a state Hook was created and used to determine whether the App component renders a Bomb component that throws an error or an error-safe component. Reset keys were also passed to the error boundary component. These reset keys determine whether the error boundary’s internal state will be reset. If one of the reset keys change during renders, the error boundary’s internal state will be reset.

On the other hand, calling the resetComponent function triggers the onResethandler of our ErrorBoundary component, where we set our explode state value to false. This causes our App component to render an error-safe component.



We also have the onResetKeysChange handler, which is triggered when the value of the reset keys change, causing a reset of the error boundary’s internal state.

useErrorHandler Hook

Another great feature of the react-error-boundary library is that it allows developers to use error boundaries to catch errors that wouldn’t otherwise be caught by traditional React error boundaries. This means we can now use error boundaries to catch errors during API requests, event handlers, and other parts of code where errors could occur.

There are two ways to use the useErrorHandler Hook:

  1. const handleError = useErrorHandler(): then we can call the handleError(error) and pass in the error object, just like in the example below
  2. useErrorHandler(error): this is useful when we’re handling the error state ourselves or when we’re getting it from another Hook

This is how we would catch errors in an API request using the first method:

 const App = () => {
  return (
    <ErrorBoundary
      FallbackComponent={CharacterFallback}
    >
      <ComponentWhereErrorMightOccur/>
    </ErrorBoundary>
  );
};


const ComponentWhereErrorMightOccur = () => {
  const handleError = useErrorHandler();
  const callAPI = () => {
    const result = fetch(apiURL)
    .then(
      (response) => response.json(),
      (error) => handleError(error))
    .then((data) => {
      return data["results"];
    });
    return result;
  };
  useEffect(() => {
    (async function () {
      await callAPI();
    })();
  }, []);
  return (
    ...
  );
};

As you can see, all we need to do is pass the error object returned from fetching data from our API to our handleError function, which was returned by the useErrorHandle Hook. This way, our error boundaries are more useful.

withErrorBoundary function as HOC

React-error-boundary allows us to utilize the withErrorBoundary function as a higher order component (HOC) to manage problems within components. This way we can focus on developing components while leaving error handling to the function. Furthermore, this method decreases the amount of code required to implement the component and its tests.

Here’s an example from the react-error-boundary docs for how to use this:

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

const ComponentWithErrorBoundary = withErrorBoundary(ComponentThatMayError, {
  FallbackComponent: ErrorBoundaryFallbackComponent,
  onError(error, info) {
    // Do something with the error
    // E.g. log to an error logging client here
  },
})

const ui = <ComponentWithErrorBoundary />

Conclusion

react-error-boundary enables React developers to reduce the amount of code that must be written and expand their error boundary capabilities to catch other forms of errors that wouldn’t otherwise be identified by regular error boundaries. Learn more about react-error-boundary on GitHub.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Yusuff Faruq Frontend web developer and anime lover from Nigeria.

One Reply to “React error handling with react-error-boundary”

Leave a Reply