React Error Boundary

In this post I’ll explain how you can use error boundaries in order to handle errors in React in a graceful way.

You’re probably familiar with React’s error screen when something goes wrong, which can be a little scary for users to see, it looks a little something like this:  

In the interest of honesty, I broke this website during the writing of this article to create that screenshot, but that is indeed how React’s error messages look during development and they aren’t much less scary in production when a user sees them. 

ERRORBOUNDARY

This is where error boundaries comes in. Since React 16, the error boundary component makes it easy for you to handle errors gracefully and have a beautifully designed error page that is not scary at all but does let the user know that something went wrong and gives them some idea of what to do.

CREATING AN ERRORBOUNDARY

Using error boundaries is pretty simple. First, create a component that looks something like this:

				
					import React, {Component} from 'react';

class MyErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = {error: false, info: null};
    }

    componentDidCatch(error, info) {
        console.error('An error was caught by your error boundary', error, info);
        this.setState({error, info});
    }

    render() {
        if (this.state.error) {
            return (
                <div>
                    <h1>Oops! Something has gone wrong.</h1>
                    <p>
                        We're very sorry about this. This error has been reported to us and we'll check it out
                        soon.
                        <hr />
                        For further enquiries, please contact us on <a href="tel:+1234567890">012 345 6789</a>.
                    </p>
                </div>
            );
        }

        return this.props.children;
    }
}

export default MyErrorBoundary;
				
			

WHAT DOES ALL OF THAT MEAN?

In the constructor, we give the component some initial state. Your app probably won’t start off with an error by default, so we set that to false.
The componentDidCatch method is then called when an error from any child component bubbles up to the error boundary. I’m sure you can see where this is going – you’re going to wrap other components in an error boundary. If they throw any exceptions, then those will bubble up and be caught by componentDidCatch.
In the render method, we check if an error has been caused. If so, display some lovely error message. If not, return the children passed into the component and go on with your life like normal.

USAGE

Now we need to actually use the error boundary. You can simply wrap another component with it and that should do it.

				
					render() {
    return (
        <ErrorBoundary>
            // ... Some page stuff
            <button onClick={() => this.somethingThatCanGoWrong()}>
                Are you sure you want to click this button?
            </button>
        </ErrorBoundary>
    );
}
				
			

When an error is thrown, the error boundary will be displayed instead of a nasty error dump which can scare your users. 

WHEN SHOULD I USE A REACT ERROR BOUNDARY?

Error boundaries don’t work for all errors in React, such as (as per the documentation):

  • Event handlers
  • Asynchronous code like timeouts
  • SSR
  • Errors thrown by the boundary itself, rather than it’s children

This means that it’s most useful to catch unanticipated errors in your code such as something being undefined when you are trying to work with it or when a service call fails.

How you use an error boundary is mostly up to you. I personally currently use it as a blanked “oops, something went wrong” component, but you can use it in a more granular fashion if you wanted to. 

FURTHER USAGE

I make use of Sentry for error tracking. My componentDidCatch method looks like this:

				
					componentDidCatch(error, info) {
    this.setState({error, info});
    Sentry.withScope(scope => {
        Object.keys(error).forEach(key => {
            scope.setExtra(key, error[key]);
        });
        Sentry.captureException(error);
    });
}
				
			

This means that, along with the error boundary being displayed, the error is also logged to Sentry so that I have a record of it so that I can fix it if required.

CONCLUSION

Error boundaries are super useful. I wouldn’t recommend going too granular with them unless you have a pretty good reason for it.