If you have built a React app at any point in your programming career, you probably have experienced an error at some point that was cryptic and did not provide any meaningful context on what actually happened.

This probably means the error occured at some point in the application and our React component did not handle the error gracefully, well mostly because it was none of its business to make sure the entire app holds.

Introducing Error Boundaries

A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an “error boundary”.

An Error Boundary is a React component that catches errors within it's children and does something meaningful with them such as post them to an error logging service or display a fallback UI for the specific child while maintaining the rest of the React app's sanity.

Therefore, for a block of functionality to be covered by Error Boundaries, it has to be a child of one in the first place.

Before we get started on an example of how you can use the React 16, beware that Error Boundaries will not catch errors in:

  1. Event handlers. ** - Use a try / catch block instead within event handlers. 2. Asynchronous code**
  2. Server side rendering
  3. Errors within the Error Boundary Component. - You cannot save others if you cannot save yourself first.

componentDidCatch()

Lifecycle methods are special functions that are invoked at different stages in the life of a component. These stages can be categorized into Mounting, Updating, Unmounting and Error handling.

For a component to be considered an Error Boundary, it has to make use of the componentDidCatch() lifecycle method to handle errors. It works in the same way that Javascript's try/catch works.

the componentDidCatch method is invoked with the an error and info parameters that contain more context on the thrown error.

Where To Use Them

Just like the granularity of React component is entirely up to you, Error Boundaries can be as specific as you want them to be. You can have a top level Error Boundary that prevents the app from crashing due to unexpected occurences and displaying a more suitable message.

For this article, we will be creating an Error Boundary that only wraps a specific functionality with errors of our own design.

Creating An Error Boundary

To get started, we will create a simple Component that only allows you to enter upto five characters into a input field. Any more than that and we break the internet. Feel free to try out in of the freely available online editors, I personally use Codepen.io.

class FiveMax extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: ''}
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.setState({ value: e.target.value})
  }

  render() {
    if(this.state.value.length > 5) {
      throw new Error('You cannot enter more than five characters!');
    }
    return (
      <div>
        <label>Type away: </label>
        <input type="text" value={this.state.value} onChange={this.handleChange} />
      </div>
    );
  }
}

ReactDOM.render(<FiveMax />, document.getElementById('root'));

If you type in more than five characters, you should get a big shiny error on your console.

Console log error

You will also notice that your input box disappers due to the error. Perfect! Let's create an Error Boundary. I'll call mine Shield.

class Shield extends React.Component {
  constructor(props) {
    super(props);
    // Add some default error states
    this.state = {
      error: false,
      info: null,
    };
  }

  componentDidCatch(error, info) {
    // Something happened to one of my children.
    // Add error to state
    this.setState({
      error: error,
      info: info,
    });
  }

  render() {
    if(this.state.error) {
      // Some error was thrown. Let's display something helpful to the user
      return (
        <div>
          <h5>Sorry. More than five characters!</h5>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.info.componentStack}
          </details>
        </div>
      );
    }
    // No errors were thrown. As you were.
    return this.props.children;
  }
}

Nothing special right? The only new thing that we have in this component that you probably haven't used before is the componentDidCatch method.

When an error is thrown in any of it's children, the error state will be updated and a meaningful error displayed. Otherwise, the Shield will go ahead and display the children as usual.

Implementing The Error Boundary

To start using the Error Boundary, we will look at two separate scenarios.

Scenario One: Two Components within the same Error Boundary

In a situation where you have two Components within the same Error Boundary and an error is thrown in one of the Components, they are both affected due to the structure of the render method in our Shield component.

To try this out, we will add two FiveMax components inside our Shield Error Boundary.

// Shield Component

// FiveMax Component

function App() {
  return (
    <div>
      <h3>Two children under one error boundary. If one crashes. Both are affected!</h3>
      <Shield>
        <FiveMax />
        <FiveMax />
      </Shield>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById('root'));

When you try typing more than five characters into any of the fields, an error is logged on the console and we get a pleasing and more informative message displayed to the user in place of both components.

Error Boundary

This is all good but we did not need to lose the other component that did not throw any errors. Let's fix that!

Scenario Two: Two Components, each of them with it's own Error Boundary

Now to prevent what happened in the scenario one, we will have each of the FiveMax components in their own Shield Error Boundary.

// Shield Component

// FiveMax Component

function App() {
  return (
    <div>
      <h3>Two children, each with their own Error Boundary. One crashes, the other is not affected</h3>
      <Shield><FiveMax /></Shield>
      <Shield><FiveMax /></Shield>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById('root'));

Now try typing more than five characters in any of the components. Notice anything? Instead of losing both components to the error, you only lose the Component that is affected. In place of the affected component only. The rest of the App remains intact!

Error Boundary 2

You can try out both scenarios in the Pen below.

See the Pen Five Max by John Kariuki (@johnkariuki) on CodePen.

Conclusion

How you use error logs entirely depends on what you want to achieve. You can have a separate Error Boundary for your navigation bar and a different one for the rest of your app so that if something goes wrong in your App's functionality, the navigation remains usable!

Remember that if your Error Boundary throws an error, anything in it's child component tree is affected. So be careful when coming up with an one so that it is not prone to errors