Handling errors gracefully in react with error boundaries
Tags: react, javascript, typescript, reactnative
Published: 2021-06-23 03:52:57

## Prerequisites to implement Error boundaries in React
* Understanding of error types such as run-time, and compile-time errors.
* Knowledge of Class based components.
* An sentry account for logging errors


## Topics covered in this blogpost
1. Architectural design pattern for implementing error boundaries in react.
2. Implementation of error boundary from scratch.
2. Types of errors catched by error boundaries.
3. Common problems faced during the usage of react error boundary.
4. react-error-boundary to the rescue.
5. Implementation of third-party error logging tools such as sentry.

## Architecture of our error boundary:
* In react, all the error boundaries are made up of class based components.
* Error boundaries are some of the graceful ways using which you can catch errors in a more efficient way.
* You can consider it as a `try` and `catch` blocks of JSX ecosystem.
* Below is a simple example of error boundaries in react

```JS
const App = () => {

  return (
    <div>
    <h1>Counter Example</h1>
      <ErrorBoundary fallBackUIComponent={<FallBackUI />}>
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}
```
* As you can see `ErrorBoundary` component is placed as a parent to a component which we suspect might cause an error.
* Whenever a run-time error occurs in the `BuggyComponent` the nearest error boundary which is `ErrorBoundary` component catches it and displays a fallback UI. Below Gif will explain this scenario.

 ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7s53r9cm06f506kjtepo.gif)

* Since the error boundary is a class based component therefore it has certain methods which it uses to catch errors. Below is the architectural diagram of the `ErrorBoundary`:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzb158192jkc4rw3h1p3.png)

## Implementation of Error Boundary:
* Before Implementing the error boundary we should keep in mind the following things:
   * Error boundary is always a `class` based component.
   * It uses following two methods to catch the errors:
      * `static getDerivedStateFromError()`: A static method which is executed before the DOM is ready(during the rendering phase of the component). This will get invoked whenever descendant component throws an error.
      * `componentDidCatch()`: This will get invoked whenever a descendant component throws an error. This component is called during `commit` phase i.e. When the DOM is ready. It can be used to perform side-effects in the component. It receives two parameters:
         * `error` - error that is being thrown.
         * `info` - An object with componentStack which tells us which component threw an error.


* Now we can move towards the implementation of the error boundary. Below code will demonstrate a class based react error boundary:

```JS
class ErrorBoundary extends React.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, errorInfo) {
    //Can be used to log to any logging service like sentry
     console.log("Catched error", errorInfo);
  }

  render(){
    if(this.state.hasError){
      return(
        // <h3>Something went wrong!</h3>
        //Can be a static or a fall-back component passed as a prop.
        this.props.fallBackUIComponent
      );
    }

    return this.props.children;
  }
}
```
Few things to note in the above implementation:
1. `getDerivedStateFromError` returns a value to update the state of the component in this case `hasError` is set to true.
2. `componentDidCatch` will also catch the error along with the stack trace of the error. This will occur on the commit phase of the component.

Lastly in the `render` function if the `hasError` state is `true` then this will print our fallback component which we passed it as a prop. Else it will return the `children`.

Usage of this component is fairly simple. Just wrap the compontent in the question with the `ErrorBoundary` Component so that it catches the error thrown by it's descendant. Below example will give you a clear idea of it's usage:
```JS
//Component for fallback UI:
const FallBackUI = () => {
  return (
    <>
    <h3>Something went wrong</h3>
      </>
  );
}

const BuggyComponent = () => {
  const [count, setCount] = React.useState(0);

  const increaseCounter = () => {
    setCount(preVal => preVal + 1);
  }
    if(count === 5) {
      throw new Error("Crashing the app!!");
    }

  return (
    <>
      <div className="counter--block">
        <span>Counter</span>
        <span>{count}</span>
      </div>
      <button onClick={increaseCounter}>Increase count</button>
      </>
  );
}

const App = () => {

  return (
    <div>
    <h1>Counter Example</h1>
      <ErrorBoundary fallBackUIComponent={<FallBackUI />}>
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}

ReactDOM.render(
<App />
  ,
  document.getElementById("root")
);
```

## Types of error catched by error boundaries
1. [React's Error Boundary](https://reactjs.org/docs/error-boundaries.html) documentation clearly states that it catches only the errors which occur during the life-cycle of a component i.e. It will catch only run-time errors.
2. Below mentioned errors are not being catched by react's error boundaries:
    >Event handlers (learn more)

    >Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)

    >Server side rendering

    >Errors thrown in the error boundary itself (rather than its children)

## Common problems faced during the usage of react error boundary:
There might be couple of reasons for `error boundary` not to work.

Some of them are mentioned below:

### **Placement of `ErrorBoundary` component.**

There are some cases where we forget that the component needs to be always wrapped with the `ErrorBoundary` component so that it catches error. Below example will provide clear understanding:

Consider a component which will throw an error when the counter value reaches `5`:

```JS
const BuggyComponent = () => {
  const [count, setCount] = React.useState(0);

  const increaseCounter = () => {
    setCount(preVal => preVal + 1);
  }
    if(count === 5) {
      throw new Error("Crashing the app!!");
    }

  return (
    <>
      <div className="counter--block">
        <span>Counter</span>
        <span>{count}</span>
      </div>
      <button onClick={increaseCounter}>Increase count</button>
      </>
  );
}
```

Placing the error boundary like below will never allow the `ErrorBoundary` Component to catch error, since the `BuggyComponent` is not being wrapped with `ErrorBoundary` but rather the content of this component is wrapped with `ErrorBoundary`.

```JS
return (
    <ErrorBoundary>
      <div className="counter--block">
        <span>Counter</span>
        <span>{count}</span>
      </div>
      <button onClick={increaseCounter}>Increase count</button>
      </ErrorBoundary>
  );
```

And also neither any of this will capture the error throw by `BuggyComponent`. To make this work we can do something like this:

```JS
const App = () => {

  return (
    <div>
    <h1>Counter Example</h1>
      <ErrorBoundary>
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}
```
Now the `ErrorBoundary` will catch the error thrown by the `BuggyComponent` since it is being wrapped by the error boundary.

### **Trying to throw a new error from event handler:**

In the above usecase as you have seen whenever the count value reaches 5 it will throw a new error.

**Note**: The `if` block for this is placed in the rendering phase of the component because of which it creates a valid case for `ErrorBoundary` to catch the error.

```JS
const BuggyComponent = () => {
  const [count, setCount] = React.useState(0);

  const increaseCounter = () => {
    setCount(preVal => preVal + 1);
  }
    if(count === 5) {
      throw new Error("Crashing the app!!");
    }

  return (
    <>
      <div className="counter--block">
        <span>Counter</span>
        <span>{count}</span>
      </div>
      <button onClick={increaseCounter}>Increase count</button>
      </>
  );
}

const App = () => {

  return (
    <div>
    <h1>Counter Example</h1>
      <ErrorBoundary>
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}
```
But the same won't work if you place the `if` block inside the `increaseCounter` function. The above example is altered to showcase this scenario:

```JS
const BuggyComponent = () => {
  const [count, setCount] = React.useState(0);

  const increaseCounter = () => {
    setCount(preVal => preVal + 1);
    if(count === 5) {
      throw new Error("Crashing the app!!");
    }
  }

  return (
    <>
      <div className="counter--block">
        <span>Counter</span>
        <span>{count}</span>
      </div>
      <button onClick={increaseCounter}>Increase count</button>
      </>
  );
}

const App = () => {

  return (
    <div>
    <h1>Counter Example</h1>
      <ErrorBoundary>
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}
```

## Alternative packages: `react-error-boundary`
`react-error-boundary` is a pretty impressive package. It solves most of the challenges faced by react's error boundary where it won't be able to catch errors such as errors thrown from event handlers, asynchornous code etc.
You can refer to the package's github [readme](https://github.com/bvaughn/react-error-boundary#readme) for more information.

Below is the implmentation of the above example but using `react-error-boundary`:

```JS
import {ErrorBoundary} from 'react-error-boundary';

function ErrorFallback({error}) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{color: 'red'}}>{error.message}</pre>
    </div>
  )
}

const BuggyCounter = () => {
  const [count, setCount] = React.useState(0);

  const handleIncrement = () => {
    setCount(preVal => preVal + 1);
  }

  if(count === 5){
      throw new Error("New Crashing Seq. Initiated");
  }

  return(
    <div className="counter--block">
      <span>Count</span>
      <span>{count}</span>
      <button onClick={handleIncrement}>Increment count</button>
      </div>
  );
}
const App = () => {
  return(
    <>
    <h1>Counter Example</h1>
    <ErrorBoundary FallbackComponent={ErrorFallback}>

        <BuggyCounter />
    </ErrorBoundary>
      </>
  )
}

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);
```

## Implmentation of third-party error logging tools
Error logging is a crucial part of any application development process. It helps us to analyze and organize errors which are not catched during the testing process of the application. These error logging tools can generally be used to moniter the errors which are thrown on the client's machine/browser.

When it comes to error logging I find [sentry.io](https://sentry.io/) to be a bliss. It has pretty impressive documentation and has wide range of support on different tech stacks such as Java, JS, React, React-Native etc.

Below is the modified example of the above example.
```JS
import React from "react";
import ReactDOM from "react-dom";
import * as Sentry from "@sentry/react";
import App from "./App";

Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" });

const BuggyCounter = () => {
    const [counter, setCounter] = useState(0);

    return (
        <>
        <div className="counter--value">
            {counter}
        </div>
        <div>
            <button
              className="counter--button"
              onClick={() => { throw new Error("New Test Error")}}>
                increment count
             </button>
        </div>
        </>
    )
}

const App = () => {
  return (
    <Sentry.ErrorBoundary fallback={"An error has occurred"}>
      <BuggyCounter />
    </Sentry.ErrorBoundary>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

// Can also use with React Concurrent Mode
// ReactDOM.createRoot(document.getElementById('root')).render(<App />);

```
In this example you need to first initialize the Sentry's instance with init function:

```JS
Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" });
```

**NOTE**: `dsn` is data source name which tells the SDK where to send the events.

Sentry also provides it's own error boundary component.
```JS
import * as Sentry from "@sentry/react";

const App = () => {
  return (
    <Sentry.ErrorBoundary fallback={"An error has occurred"}>
      <BuggyCounter />
    </Sentry.ErrorBoundary>
  );
}
```

You can find the code used in this blogpost below:
1. Implementation of react error boundary from scratch:
https://codepen.io/keyurparalkar/pen/LYWJKvm?editors=0010

2. Implementation of react error boundary using `react-error-boundary` package:
https://codepen.io/keyurparalkar/pen/bGqQNJe

Feel free to reach out to me @
<div style="display:flex;flex-direction:row;justify-content:center">

[![Linkedin Badge](https://img.shields.io/badge/linkedin-%230077B5.svg?&style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/keyur-paralkar-494415107/)

[![Mail Badge](https://img.shields.io/badge/email-c14438?style=for-the-badge&logo=Gmail&logoColor=white&link=mailto:furkanozbek1995@gmail.com)](mailto:keur.plkar@gmail.com)

[![Twitter Badge](https://img.shields.io/badge/twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/keurplkar)

[![Github Badge](https://img.shields.io/badge/github-333?style=for-the-badge&logo=github&logoColor=white)](https://github.com/keyurparalkar/)