Simple error handling in React with react-error-boundary

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 of such method is the use of error boundaries.

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 us developers to implement error boundaries in our code without building our own 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.

The 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 our 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 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 you 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 onReset handler 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.

The 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.

This is how we would catch errors in an API request:

 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.

Conclusion

react-error-boundary enables us React developers to reduce the amount of code we have to write and expand the capabilities of our error boundaries to catch other forms of errors that wouldn’t otherwise be caught by regular error boundaries. You can learn more about react-error-boundary here.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult 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 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 — .

Author: admin

Leave a Reply

Your email address will not be published.