Handle errors gracefully with React Error Boundary

Yogini Bende - Apr 20 '21 - - Dev Community

Hello Folks,

While developing any project in React, we mostly deal with conditional operations or passing elements to certain components, functions etc. But if anything unexpected happens to the React component or function and a runtime error occurs, most of the time you see a white screen of death!.😑 Now, if you open your developer tools and check the console, you see the error. But this is definitely not the recommended way for handling this.

This runtime error or white screen errors should be handled gracefully and that is where React Error Boundary comes in the picture. React has added error boundaries to catch the javascript error and handle them efficiently. As per react documentation, Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Till the date of writing this article, react boundaries are supported as class components only. Hence, while using React with hooks, this might be the only class component you will need.

Enough of theory, let's dive in to the code now -

Let’s first create a class component and use it as an error boundary. Here is the code -

class ErrorBoundary extends Component {
    state = {
        error: null,
    };
    static getDerivedStateFromError(error) {
        return { error };
    }
    render() {
        const { error } = this.state;

        if (error) {
            return (
                <div>
                    <p>Seems like an error occured!</p>
                    <p>{error.message}</p>
                </div>
            );
        }
        return this.props.children;
    }
}

export default ErrorBoundary;

Enter fullscreen mode Exit fullscreen mode

In the above code, you will see a static method getDerivedStateFromError(error). This method will turn the ErrorBoundary class component into a component that actually handles errors.

Here, we are catching the error in the getDerivedStateFromError method and setting it as state. If the error is present we are printing it (for now) and if there is no error we are just returning the control back to the original element.

Now let’s see where we can use this error boundary. Consider you are printing a users list which is fetched from an API. It will look something like this -

const Users = ({ userData, handleMoreDetails }) => {
    return (
        <div>
            <h1>Users List: </h1>

            <ul>
                {userData.map((user) => (
                    <div key={user.id}>
                        <p>Name: {user.name}</p>
                        <p>Company: {user.company}</p>
                        <button onClick={() => handleMoreDetails(user.id)}>
                            More details
                        </button>
                    </div>
                ))}
            </ul>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

The above user component will work completely fine till it is getting userData. But if, due to some reason, the userData is undefined or null, our application will be broken! So now, let’s add our error boundary to this component. Hence, the updated code will look like this -



const Users = ({ userData, handleMoreDetails }) => {
    return (
        <div>
            <h1>Users List: </h1>
            <ErrorBoundary>
                <ul>
                    {userData.map((user) => (
                        <div key={user.id}>
                            <p>Name: {user.name}</p>
                            <p>Company: {user.company}</p>
                            <button onClick={() => handleMoreDetails(user.id)}>
                                More details
                            </button>
                        </div>
                    ))}
                </ul>
            </ErrorBoundary>
        </div>
    );
};


Enter fullscreen mode Exit fullscreen mode

Here, when the error occurs, our ErrorBoundary component will catch that and the error message will get printed to the screen. This will save the application from breaking and the user will also understand what is wrong.

Important point to consider here is the place where we have used the error boundary. Error boundary will display error instead of component. So we always need to make sure where we want to place that error. In our example, we definitely want to show heading of the page and other details too. We just want to replace the component where error occurred and in this case, it is just the ul element. Hence we have wrapped only the ul element inside the error boundary and not the entire component.

Till now, we have already understood what the error boundary is and how to use it. But our fallback display of error boundaries (where errors are displayed) is not usable and can be improved. The way we show our errors and fallback components will be different for different cases in a single application. So we will need to make our Error Boundary component more generic so that all these fallback UIs can be used.

For this we will create a prop ErrorComponent in the error boundary and return the element passed to this prop whenever the error occurs. Here is the final code of both ErrorBoundary and User components -

// User Component 

const Users = ({ userData, handleMoreDetails }) => {
    const ErrorMsg = (error) => {
        return (
            <div>
                {/* You can use your own styling and methods of handling error */}
                <p>Something went wrong!</p>
                <p>{error.message}</p>
            </div>
        );
    };

    return (
        <div>
            <h1>Users List: </h1>
            <ErrorBoundary ErrorComponent={ErrorMsg}>
                <ul>
                    {userData.map((user) => (
                        <div key={user.id}>
                            <p>Name: {user.name}</p>
                            <p>Company: {user.company}</p>
                            <button onClick={() => handleMoreDetails(user.id)}>
                                More details
                            </button>
                        </div>
                    ))}
                </ul>
            </ErrorBoundary>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode
// ErrorBoundary Component
class ErrorBoundary extends Component {
    state = {
        error: null,
    };
    static getDerivedStateFromError(error) {
        return { error };
    }
    render() {
        const { error } = this.state;

        if (error) {
            return <this.props.ErrorComponent error={error} />;
        }
        return this.props.children;
    }
}
Enter fullscreen mode Exit fullscreen mode

You can pass key prop as well to your error boundary, if you need to use it multiple times in a single component. This will remove the previous error state from the error boundary and will show the correct element in every render.

Error boundary is one of the really nice features React has and I have seen it is comparatively less used. But using this in your code will surely save you from the awkward moments with an unexpected error. And who doesn't want better error handling.😉

In case you don’t want to write your own error boundary component, there is one awesome package for this. Here is a link react-error-boundary

So that was it from this article. Please share your thoughts/comments/feedback. You can also connect with me on Twitter or buy me a coffee if you like my articles.

Keep learning.🙌

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .