DEV Community

Cover image for Optimizing React Components with Error Boundaries
Johnson Ogwuru
Johnson Ogwuru

Posted on

Optimizing React Components with Error Boundaries

Disclaimer: The concept discussed in this article is part of the advanced concepts of React as outlined in the official docs. But I see it as something every react developer should know and use. You would find this my view to be accurate at the end of this tutorial.

The React we know:

By its original design, when JavaScript errors occur in a component as little as a Button component, it leads to the complete crash and failure of the react application. At the time react did not provide a way to handle this, neither was it able to recover from these errors when they occurred.

But now, the React team at facebook with the advent of React 16, introduced a new concept for error handling called error boundaries.

Introducing Error Boundaries:

With the introduction of error boundaries, components can have errors and crash in peace without having to affect the entire application state causing an application-wide crash

According to the 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. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Error boundaries work like the catch(){} block in JavaScript, but this time for components. Someone could say since it works like JavaScripts catch(){} block, why don't we use try/catch of creating error boundaries. So we could have something like this:

      try { 
         <ShowButton /> 
      } catch(err) {
         console.log(err)
      }
Enter fullscreen mode Exit fullscreen mode

This looks good and could solve the problem we want to solve with Error Boundaries, but the thing is try/catch only works for imperative code, but since we are working with react, which is declarative, react components are declarative and specify what should be rendered. But with Error Boundaries the declarative nature of React is preserved.

Just to add to this article, to understand the difference between imperative code and declarative code, look at these articles, here and
here


Error Boundaries, can only be class components, how do I mean, you can't use functional components, to create an error boundary, but a class component.

For a class component to be considered an error boundary, it needs to have either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch(). Where getDerivedStateFromError() is used to render a fallback UI with the error response the developer specifies, and componentDidCatch() is used to log error information, so here you could be using any log service of your choosing, or our favorite console.log.


To jump into code, this is what an error boundary looks like;

 import React, {Component} from 'react';
 class ErrorBoundary extends 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, info) {
       // You can also log the error to an error reporting service
       logErrorToMyService(error, info);
    }

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

      return this.props.children; 
    }
 }
Enter fullscreen mode Exit fullscreen mode

The code example was extracted from the react documentation, and for further reads, I would suggest you likely have a look at it.

With Error boundaries in place in our code, we won't have a mere error in our button component render, kill the entire application, we would have complete control over such things, and the users of the application won't be left wondering what happened. confused gif

To use the created error boundary, all we have to do is, wrap any component we want to be covered by error boundary within it as so;

Lastly, it's important to note that, error boundaries can't be used within event listeners. When dealing with event listeners, it's best to use the try/catch block.

Note: How you want to use error boundaries, is totally up to you, you could have your entire application wrapped in an error boundary or simply have individual components wrapped within an error boundary. But, it is still highly recommended that you just put your in a few strategic places.

For further reads on error boundaries, I won't recommend other sources than the react documentation here

I hope you enjoy using error boundaries as I do. That's it from me here, if you have any questions or feedback feel free to comment or DM on twitter

Top comments (2)

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the post, Johnson. Your post is a good based on the official doc~

One thing I'd like to mention is that, when an error boundary catches and error, it normally shows a "fallback UI".

 class ErrorBoundary extends Component {
    // ... rest removed for brevity

    render() {
      if (this.state.hasError) {
        // 👇 here shows the fallback UI
        return <h1>Something went wrong.</h1>;
      }

      return this.props.children; 
    }
 }

But once it shows the fallback, there isn't a documentation on how to "clear" the error boundary (if it's a non-critical error as an example).

I asked for a feature for react-error-boundary (by Brian Vaughn of React core team) to enable "clearing the boundary".

It's basically a bit more mature version of what's in the documentation.

And his response was to add a "key" to the Error Boundary component and change the key to re-render the children.

github.com/bvaughn/react-error-bou...

class App extends React.Component {
  state = {
    errorBoundaryKey: 0
  };

  handleRerenderButtonClick = () => this.forceUpdate();

  handleResetButtonClick = () =>
    this.setState(prevState => ({
      errorBoundaryKey: prevState.errorBoundaryKey + 1
    }));

  render() {
    return (
      <div className="App">
        <button onClick={this.handleRerenderButtonClick}>re-render</button>
        <button onClick={this.handleResetButtonClick}>
          reset error boundary
        </button>
        {/*           ... 👇 ... */}
        <ErrorBoundary key={this.state.errorBoundaryKey}>
          <ComponentThatMayError />
        </ErrorBoundary>
      </div>
    );
  }
}
Collapse
 
ogwurujohnson profile image
Johnson Ogwuru

wow, makes sense, Thanks for sharing this Sung