Understanding React's Error Boundaries
Tags: javascript, react, webdev
Published: 2020-06-08 12:13:55

*This post was originally published in [my personal site](https://francodalessio.com/understanding-react-error-boundaries/)*.

In the past, **Javascript errors inside components used to corrupt React’s internal state and produce a corrupted UI in place**. React did not provide any way to handle these errors gracefully, and could not recover from them.

An important change was introduced in **React version 16**. Now **any uncaught Javascript error will result in unmounting the whole React component tree**. This leaves no room for corrupted UIs.

Why was this change introduced? Well, the React team believes that **it is a bad practice to leave a corrupted UI in place**, because it may have dangerous outcomes. For example, in a bank app, it is worse to display a wrong account balance than to render nothing.

That sounds logical, but still **a Javascript error in some part of the UI shouldn’t break the whole app**. To solve this problem, the React team introduced a new concept called **error boundary**.

## What is an error boundary?

Error boundaries are React components. Their role is to **catch Javascript errors** anywhere in their child component tree, **log them** and **display an alternative UI** instead of the component tree that crashed.

Error boundaries catch errors during:

- Rendering
- Lifecycle methods
- Constructors

But it's important to know that error boundaries **do not** catch errors for:

- Event handlers
- Async code
- Server side rendering (SSR)
- Errors thrown in the error boundary itself

Error boundaries work like a Javascript `catch {}` block, but for components.

## Before we create an error boundary component...

We need some app to test it. So let's create a very simple UI where we have two sections: `News` and `Chat`.

Both sections have a button that will **simulate a Javascript error** when clicked.

This is our `News` component:

```jsx
import React from "react";

const styles = {
  newsBox: {
    border: "1px solid #333",
    margin: "0.5rem 0",
    height: "50px",
    width: "300px",
  },
};

const News = () => {
  const [error, setError] = React.useState(false);

  const handleClick = () => {
    setError(true);
  };

  if (error) throw new Error("News error!");

  return (
    <>
      <h2>News</h2>
      <div style={styles.newsBox} />
      <div style={styles.newsBox} />
      <button onClick={handleClick}>Throw Error</button>
    </>
  );
};

export default News;
```

The `Chat` component, very similar to the previous one:

```jsx
import React from "react";

const Chat = () => {
  const styles = {
    chatBox: {
      border: "1px solid #333",
      margin: "0.5rem 0",
      height: "150px",
      width: "300px",
    },
  };

  const [error, setError] = React.useState(false);

  const handleClick = () => {
    setError(true);
  };

  if (error) throw new Error("News error!");

  return (
    <>
      <h2>Chat</h2>
      <div style={styles.chatBox} />
      <button onClick={handleClick}>Throw Error</button>
    </>
  );
};

export default Chat;
```

And our `App` component:

```jsx
import React from "react";
import News from "./News";
import Chat from "./Chat";

export default function App() {
  return (
    <div style={{ padding: "0.5rem 1.5rem" }}>
      <h1>Welcome!</h1>
      <hr />
      <News />
      <hr />
      <Chat />
    </div>
  );
}
```

Our app looks like this:

![Our simple application](https://dev-to-uploads.s3.amazonaws.com/i/t6fj61k5r20pd98nkyom.png)

Now let's see what happens when a Javascript error is thrown.

## Without error boundaries

If we click one of the `Throw Error` buttons, **we would expect the whole app to break**. As we previously discussed, React 16 has this behavior for any uncaught Javascript error.

And effectively, it does break:

![Our breaking app](https://dev-to-uploads.s3.amazonaws.com/i/ebslya83end829juzlou.gif)

Now let's see how to catch these errors with an error boundary component.

## How to create an error boundary component

Creating an error boundary component is very easy. The first thing you should know is that error boundaries have to be **class** components. Right now there is no way to create an error boundary using a functional component.

```jsx
import React from 'react';

export default class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return...
  }
}
```

The next thing you should do is adding the `componentDidCatch` method. This method receives two parameters: the `error` and the `errorInfo`.

As we want to display a **fallback UI** in case of error, we need to have some **state** that indicates that. So let's add it, and update the state when an error is caught:

```jsx
import React from 'react';

export default class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });

    // Log error info somewhere
  }

  render() {
    return ...
  }
}
```

You could also log the `errorInfo` somewhere.

Great! The last step is to complete the render function. We want to render a fallback UI if there is an error. Otherwise, we just need to render the `children`.

```jsx
import React from "react";

export default class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });

    // Log error info somewhere
  }

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

Perfect! We have our error boundary component, now let's see how we can use it.

## Catching errors with an error boundary component

Using our new error boundary component is very easy. We just need to include it in our component tree as a **wrapper**:

```jsx
export default function App() {
  return (
    <ErrorBoundary>
      <div style={{ padding: "0.5rem 1.5rem" }}>
        <h1>Welcome!</h1>
        <hr />
        <News />
        <hr />
        <Chat />
      </div>
    </ErrorBoundary>
  );
}
```

This way, when a Javascript error is thrown, it will be caught by our error boundary and the fallback UI will be displayed instead:

![Our error boundary component catches the error](https://dev-to-uploads.s3.amazonaws.com/i/wcga6kgc01vdsudnznkv.gif)

## Using multiple error boundaries

The previous example worked fine, we were able to catch our error and display a fallback UI.

However, **our entire application was replaced by this error message**, even though the error was thrown by only one of the components.

The good news is that you can have **multiple error boundaries for different sections** of your application.

For example, Facebook Messenger wraps content of the sidebar, the info panel, the conversation log, and the message input into separate error boundaries. If some component in one of these UI areas crashes, the rest of them remain interactive.

In our case, we could use an error boundary for our `News` component, and another one for our `Chat` component:

```jsx
export default function App() {
  return (
    <div style={{ padding: "0.5rem 1.5rem" }}>
      <h1>Welcome!</h1>
      <hr />
      <ErrorBoundary>
        <News />
      </ErrorBoundary>
      <hr />
      <ErrorBoundary>
        <Chat />
      </ErrorBoundary>
    </div>
  );
}
```

Now, if our `News` section throws a Javascript error, our `Chat` section won't be affected:

![Multiple error boundary components catch different errors](https://dev-to-uploads.s3.amazonaws.com/i/p5jf46mdfxg9b6ulk5t0.gif)

And this is the real _power_ of error boundaries. If you use multiple of them, you can **isolate** errors in **different, independent sections**, without affecting the whole application.

## That's it!

I hope this post was helpful and now you understand better what error boundaries are and how to use them. Thanks for reading! ❤️