React 16 - Taking control of your errors using Error Boundaries

React 16 is here, and a lot of new features have been introduced. One of these features is Error Boundaries.

As you probably have experienced - errors that occur in the frontend have a tendency to propagate across the whole app, leaving it broken in an uncontrolled manner.

With error boundaries we now got an easy way of controlling errors happening when rendering React components.

How can Error Boundaries help us to take control of errors?

An error boundary is simply a react component. By wrapping other components in an error boundary we'll get the chance to act on errors that propagate from these components during rendering.

This way we can take control of the error handling of different parts of the application. Error boundaries help us isolate the problems however we want. With the help of these boundaries we could end up with one part of the UI being perfectly fine, while other parts ending up using fallbacks.

This results in resilient applications which will give a better user experience.

How to create an Error Boundary

Creating an error boundary is easy. All we have to do is to define the new lifecycle method componentDidCatch in a react component.

This lifecycle will be triggered if any errors occur during rendering of the child components. This way we can limit the impact and create a fallback for the components within the boundary.

Now that we know what we're dealing with, let's implement an error boundary.

class ErrorBoundary extends React.Component {  
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <div>Something went wrong!</div>;
    }
    return this.props.children;
  }
}

As you can see, we got componentDidCatch and render defined.

In componentDidCatch we simply set an error boolean in the state. Then we make sure to check the state for any errors in the render method to see if we should render the fallback message - "Something went wrong!".

That's it! Now we can limit the damages if any error occurs.

Using the Error Boundary

Now that we got our error boundary defined, let's start using it.

Say we got two main components in our UI - LeftComponent and RightComponent.

If an error occurs in one or the other component, we want to limit the damages by showing a user friendly error message for the failed component. The component that renders successfully should be unaffected.

To do this, we wrap each of the components with the error boundary we created earlier.

const App = () => (  
  <div>
    <ErrorBoundary><LeftComponent/></ErrorBoundary>
    <ErrorBoundary><RightComponent/></ErrorBoundary>
  </div>
);

Done! Now, if an error is triggered in either LeftComponent or RightComponent, the error will be encapsulated and handled by the error boundary it's wrapped inside.

Keeping the Error Boundary component clean

You should try to keep the error boundary component stripped of as much logic as possible. Why? Because it only catches errors in child components. This means that errors occuring in the code of the error boundary itself will propagate to an outer error boundary.

So try to keep the error boundaries focused on the task it's actually made for.

Custom Fallback UI through Higher Order Components

Error boundaries is a critical part to get right. Because of this, it could be a good idea to only have it declared once in your application.

But how can we keep only one error boundary implementation and still have different fallback for different components?

Because of Reacts composable nature it's easy to make small reusable parts, which makes it trivial to extract every aspect of the error boundary we want to customise, but leaving the essential mechanisms of the error boundary.

Going back to our example of LeftComponent and RightComponent, say we want to have a custom fallback UI for each of the components but we want to keep the lifecycle and the error check inside the one error boundary declaration.

We could solve this by modifying our error boundary into a higher order component.

function createErrorBoundary(FallbackUi) {  
    return class ErrorBoundary extends React.Component {
        constructor(props) {
            super(props);
            this.state = { hasError: false };
        }

        componentDidCatch(error, info) {
            this.setState({ hasError: true });
        }

        render() {
            if (this.state.hasError) {
                return <FallbackUi />;
            }
            return this.props.children;
        }
    }
}

Now that we got a higher order component, let's use it to create error boundaries for LeftComponent and RightComponent with customized fallbacks.

const LeftErrorBoundary = createErrorBoundary(() => <div>Could not render left</div>);  
const RightErrorBoundary = createErrorBoundary(() => <div>Could not render right</div>);

const App = () => (  
    <div>
        <LeftErrorBoundary><LeftComponent/></LeftErrorBoundary>
        <RightErrorBoundary><RightComponent/></RightErrorBoundary>
    </div>
);

As you can see, Error Boundaries is a simple way to get control of your errors. It's easy to use and aligns well with the composable nature of React.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe