Javascript errors inside components can corrupt the state of a React application. This can cause strange errors. Before React Error Boundary, there was no way to recover from these errors.

React Error Boundary is a special component that can catch errors within its child hierarchy. It can also log the errors and display a fallback UI in case of error. In other words, React Error Boundaries are a lot like try/catch blocks. However, there are some differences between try catch block and error boundaries.

In this post, we will look at the how to write error boundaries in React with examples.

1 – Creating a React Error Boundary Component

To understand the concept of error boundaries in React, let us look at an example.

We will create a simple React functional component as below:

const NormalComponent = () => {
    return (
        <div>
            <h2>{title.toUpperCase()}</h2>
            <p>This is a normal component</p>
        </div>
    )
}

export default NormalComponent;

This component expects a prop named title. We call the toUpperCase() function on the string. If we don’t pass the prop while using this component, it will throw an error as below.

Uncaught ReferenceError: title is not defined

Basically, this is a run-time error. Though this example is a little contrived but run-time errors can happen all the time. By using React Error Boundary, we can handle these errors in a better way.

But, how do you write error boundaries in React?

To write an error boundary in React, we need to declare a component that satisfies a couple of conditions. Firstly, the component must be a class component. Second, it must implement either getDerivedStateFromError() or the componentDidCatch() methods.

See below example:

import React from 'react';

class LocalErrorBoundary extends React.Component {
    state = {error: null}

    static getDerivedStateFromError(error) {
        return { error };
    }

    componentDidCatch(error, errorInfo) {
        console.log('logErrorToService: ', errorInfo)
    }

    render() {
        const { error } = this.state
        if(error) {
            return(<p>There is some issue! Please try again...</p>)
        }

        return this.props.children
    }
}

export default LocalErrorBoundary;

In the above React class component, we have implemented both getDerivedStateFromError() and componentDidCatch() methods. Also, depending on whether there is an error, we return a special HTML markup. You can think of it as a fallback markup.

Now, we can simply wrap our component within the error boundary.

return (
    <div className="App">
      <LocalErrorBoundary>
        <NormalComponent />
      </LocalErrorBoundary>
    </div>
)

This time, when there is a run-time error in the NormalComponent, the Error Boundary component will handle the same. Instead of showing an error, we will see the fallback message in the browser. Also, the error information will be logged.

2 – Difference Between try catch blocks and error boundaries

At this point, we might wonder what is the difference between try catch blocks and error boundaries in React.

The main difference between try catch blocks and React error boundaries is that error boundaries are declarative in nature. On the other hand, typical try catch blocks are imperative. This makes using try catch blocks a lot more cumbersome. For a large application, it is not possible to wrap each and every component within a try catch block.

See below example of using a try catch block inside a React component.

import React from 'react';
import FallbackComponent from './FallbackComponent';

const TryCatchDemo = () => {
    try {
        return <div>Hello {subject.toUpperCase()}</div>
    } catch (error) {
        return <FallbackComponent error={error} />
    }
}

export default TryCatchDemo;

The piece of JSX where we might expect a run-time error is enclosed by a try catch block. In the case of error, we render another component or a different markup.

While try-catch blocks work, imagine doing this within every component of your component hierarchy. On the other hand, React error boundaries make the logic of handling run-time errors far more easy to understand and maintain.

3 – Using the react-error-boundary package

While it is certainly possible to create our own Error Boundary component in React, it is simpler to use the react-error-boundary npm package. You can read more about this package on the official docs.

The react-error-boundary package provides additional features such as resetting an error state and fallback component.

To demonstrate the use of react-error-boundary package, let us create a simple component that may throw run-time error.

function RecoveryDemoComponent({ errorStatus }) {
    if (errorStatus === 'error') {
        throw new Error('Error Situation!')
    }

    return 'No Error!'
}

export default RecoveryDemoComponent;

Basically, this component throws an error if the prop errorStatus gets a value of ‘error’. In all other cases, it simply returns ‘No Error!’.

Now, we can use this component in another component. See below:

import React from 'react';
import {ErrorBoundary} from 'react-error-boundary';
import FallbackComponent from './FallbackComponent';
import RecoveryDemoComponent from './RecoveryDemoComponent';

function ErrorBoundaryDemoComponent() {

  const [errorStatus, setErrorStatus] = React.useState('');

  return (
    <div className="App">

      <label>
        Type Error to generate error
        <input
          value={errorStatus}
          onChange={e => setErrorStatus(e.target.value)} />
      </label>
      <ErrorBoundary FallbackComponent={FallbackComponent}
      onReset={() => {
        setErrorStatus('')
      }} >
        <RecoveryDemoComponent errorStatus={errorStatus} />
      </ErrorBoundary>
    </div>
  );
}

export default ErrorBoundaryDemoComponent;

As you can see, we have an input field for errorStatus. We pass the user input to the RecoveryDemoComponent. Since this component can potentially throw a run-time error, we wrap it inside the ErrorBoundary component imported from the react-error-boundary package.

The ErrorBoundary component takes FallbackComponent as a prop. In case of an error, it will render the FallbackComponent. Below is the code for the Fallback component. Here, we have some information text and the error message.

import React from 'react';

function FallbackComponent({ error , resetErrorBoundary}) {
    return (
        <div role="alert">
            <p>Something went wrong!</p>
            <pre style={{ color: 'red' }}>{error.message}</pre>
            <button onClick={resetErrorBoundary}>Try again</button>
        </div>
    )
}

export default FallbackComponent;

The ErrorBoundary component allows us to reset the error boundary. To do so, it provides the onReset() prop. Basically, this prop receives a callback function. We can use the callback function to rectify the error situation.

When the Try Again button within the FallbackComponent is clicked, the callback defined with the onReset() is triggered. In our example, we use it to reset the errorStatus to a blank value.

Conclusion

React Error Boundary is a powerful tool to handle run-time errors gracefully. It handles the errors in a declarative approach. This makes error boundaries a superior alternative to try-catch blocks.

While developing applications, we should definitely use them as much as possible.

Want to learn more about React? Check out this post about React Fragment vs Normal Div.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: React

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *