What you may not know about the Error Boundary
Tags: react, tutorial
Published: 2020-10-23 19:20:52


**Prerequisite: Basic knowledge about [React](https://reactjs.org/)**

I believe that you might know about [Error Boundaries](https://reactjs.org/docs/error-boundaries.html) **BUT** do you know how to recover a component from an error? 😌 Do you know how to survive through the pandemic?

<img src="https://user-images.githubusercontent.com/26871154/97011917-f1e74f80-154f-11eb-9bd9-8f5c36f82e11.jpg" width="600" title="You know it"/>

You might think our app is doing just fine **UNTIL...**

**...it's 2020**, let's talk about how Error Boundaries can **protect our children** components🦠

I decided to write this blog since I haven't seen many projects I worked with utilized the Error Boundary 👻 Even my friend who I really trust does not use it 😭

💪 Let's get started!

## 1. What's wrong?

What will happen when we run the below code?

```jsx
import React from 'react'

const App = () => {

    return (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>) 🤧
}
```

You got it right. The icon 🤧 breaks the code and even after removing the icon, we will see the blank screen instead of the text. Open the console, we could see the error `vaccine` is not defined. We'd better show sth nicer to the user when the app crashes 😌

In a real-world project, it is not always obvious as the example above. It could be that the API doesn't return some important data that could easily break our application or we forget to pass some props to the component. Everything works fine until the pandemic hits us. 😷

## 2. Try - catch

```jsx
import React from 'react'

const App = () => {
    try {
        return (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>)
    } catch {
        return (<p>Quarantine<p/>)
    }   
}
```

Perfect, it works 🙃 Now React renders the text `Quarantine` instead of a blank screen. It tells the user that something is wrong. But imagine that you have to wrap the try-catch block with every component ... it would be a nightmare

> git commit -am "fix: wrap component within try-catch"

🎅 When you go to sleep, Santa comes and refactors the code

```jsx
import React from 'react'

const Flu = () => (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>)

const App = () => {
    try {
        return (<Flu/>)
    } catch {
        return (<p>Quarantine<p/>)
    }   
}
```

It breaks again 🥴 Blank screen again 👀 Your try-catch block doesn't work anymore.

🤔 Why?

You can learn more about that Dark magic [here](https://stackoverflow.com/questions/49456018/how-is-componentdidcatch-different-than-a-try-catch-in-render)

> What if Santa has coronavirus and visits everybody at night?

## Error Boundary

### ⭐ What is this?


Error Boundary is a React special component to catch any JavaScript errors anywhere in their child component tree. Basically, it is like a try-catch block **but for the component**. It must be a class component which must define either `static getDerivedStateFromError()` or `componentDidCatch()`

According to the React docs, we use `static getDerivedStateFromError()` to render a fallback UI after an error has been thrown. Use `componentDidCatch()` to log error information.

```jsx 
class ErrorBoundary extends React.Component {
  state = {error: null}

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { error };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    console.log('logErrorToService: ', errorInfo);
  }

  render() {
    const {error} = this.state
    if (error) {
        return (<p>Quarantine 🤒</p>)
    }

    return this.props.children; 
  }
}
``` 

Look at these lines, if there is an error then we return the fallback component, else return the children. 

```
  render() {
    const {error} = this.state
    if (error) {
        return (<p>Quarantine 🤒<p/>)
    }

    return this.props.children; 
  }
```

To use ErrorBoundary, we have to wrap our component within it

```jsx
import React from 'react'

const Flu = () => (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>)

const App = () => {
   return (<Flu/>)
}
```

```jsx
<ErrorBoundary>
  <App />
</ErrorBoundary>
```

Nice, now we see the text `Quarantine` which is our fallback component again instead of the blank screen. You can wrap top-level route components within ErrorBoundary (lock down the whole city 🦠) or whatever component that you want.It works just like a try-catch block 😇 

```jsx
import React from 'react'

const Flu = () => (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>)

const App = () => {
   return (
    <div>
        <h1>Got you<h1>
        <ErrorBoundary><Flu/></ErrorBoundary>
    </div>
    )
}
```

### ⭐ Get better

However, we don't always want `Quarantine` when we get the error. Let's pass the Fallback component to the Error Boundary instead. 

```jsx 
class ErrorBoundary extends React.Component {
    
  .....

  render() {
    const {error} = this.state
    if (error) {
        return (<this.props.FallbackComponent error={error}/>)
    }

    return this.props.children; 
  }
}
``` 

Now, whoever uses our ErrorBoundary component can decide what they would display as the fallback. Notice that we can pass the `error` props to the Fallback component.

```jsx

const ErrorFallback = ({error}) => (<p>Quarantine</p>)

<ErrorBoundary FallbackComponent={ErrorFallback}>
  <App />
</ErrorBoundary>
```

### ⭐ Recovery

Now we will take a look at how we can **recover a component** from an error.

Our use-case is a small Counter application. Every time we click on the button, the counter will increase by one. When the count value is equal to 3, the error will be thrown 💣 Pay attention to the Counter component

```jsx
const Counter = ({count}) => {

   React.useEffect(() => {
      if (count === 3) {
         throw Error("Crash")
      }
   })

   return <p>{count}</p>
}

const ErrorFallback = () => (<p>Something went wrong</p>)

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

   function onClick() {
      setCount(count => count + 1)
   }

   return (
      <div>
         <button onClick={onClick}>click</button>
         <ErrorBoundary FallbackComponent={ErrorFallback}>
            <Counter count={count} />
         </ErrorBoundary>
      </div>
   )
}
```

What will happen if we click the button 4 times?

🤥 A: The Counter will show number 4

☠️ B: The app will crash

🤞 C: The Counter will show "Something went wrong"

.
.
.
🚨 SPOILER ALERT
.
.
.

The correct answer is **C**

Because we wrap the `Counter` component within ErrorBoundary, the app will not crash when the error is thrown. Instead, you would see the fallback UI `Something went wrong` when you click the button 3 times. After that, it would still show the fallback UI even when you keep clicking the button. This means that our component is DEAD

![error-not-recover](https://user-images.githubusercontent.com/26871154/97033296-cd00d580-156b-11eb-9784-3767ab1410a4.gif)


This would be not ideal in some cases. For example, the app should only show an error when the user searches for the missing data (let's pretend the app would crash when the server returns empty). But then, if the user changes the query, the app should work as normal instead of showing an error. In our case, the app should still work when we click the button.

This could be done simply by adding the unique `key` prop to the ErrorBoundary. When the key changes, the ErrorBoundary will be unmounted and remounted. In our application, we would like to reset the ErrorBoundary and rerender the Counter when the `count` value changes. 

```jsx
const App = () => {
   const [count, setCount] = React.useState(0)

   function onClick() {
      setCount(count => count + 1)
   }

   return (
      <div>
         <button onClick={onClick}>click</button>
         <ErrorBoundary key={count} FallbackComponent={ErrorFallback}>
            <Counter count={count} />
         </ErrorBoundary>
      </div>
   )
}
```

![error-recover](https://user-images.githubusercontent.com/26871154/97033275-c1adaa00-156b-11eb-969e-6c12d44312e0.gif)

## 3. A vaccine with react-error-boundary:

Let's install the react-error-boundary 

> npm install react-error-boundary

Then we can import the ErrorBoundary without having to write the component ourselves. Moreover, this version also has some cool features.

```jsx
const ErrorFallback = ({error, resetErrorBoundary}) => (
   <div>
   <p>Something went wrong</p>
   <button onClick={resetErrorBoundary}>Try again</button>
</div>
)

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

   function onClick() {
      setCount(count => count + 1)
   }

    function throwError() {
       setCount(3) // count = 3 will cause error
    }

    function handleReset() {
       setCount(0)
    }

   return (
      <div>
         <button onClick={onClick}>click</button>
         <button onClick={onClick}>throw error</button>
         <ErrorBoundary FallbackComponent={ErrorFallback} onRest={handleReset}>
            <DisplayCount count={count} />
         </ErrorBoundary>
      </div>
   )
}
```

Pay attention to our ErrorFallback component, you can see that The ErrorBoundary passes the `resetErrorBoundary` callback to the Fallback component. With this function, we can explicitly reset the state of ErrorBoundary by clicking on the Try again button. 

We also pass an extra `onRest` prop to the ErrorBoundary component which will be triggered when the ErrorBoundary is reset. In this way, we can reset the count value to 0 when the user clicks on the try again button.

![react-error-boundary](https://user-images.githubusercontent.com/26871154/97040500-b1e79300-1576-11eb-918a-00fa16b51da1.gif)

However, do you notice we are missing the behavior that the ErrorBoundary resets itself when the count value changes? Let's bring that feature back bypassing the `resetKeys` props to the component. That prop is exactly like our previous key props but it can receive an array instead of a single value.

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

    .....

   return (
      <div>
         <button onClick={onClick}>click</button>
         <button onClick={onClick}>throw error</button>
         <ErrorBoundary FallbackComponent={ErrorFallback} onRest={handleReset} resetKeys={[count]>
            <DisplayCount count={count} />
         </ErrorBoundary>
      </div>
   )
}
```

![reset-keys](https://user-images.githubusercontent.com/26871154/97041077-9d57ca80-1577-11eb-9fbf-eb9b5d8780ee.gif)

🚨 Error Boundary also works best with React suspense for Data Fetching which is just the experimental feature. I might update this blog in the future 😇

## 4. Conclusion:

😷 It's not mandatory but we can consider using ErrorBoundary to protect our app by catching an unexpected error

Here are some good resources for you:
- [Error Boundaries](https://reactjs.org/docs/error-boundaries.html)
- [react-error-boundary](https://github.com/bvaughn/react-error-boundary)


## 🙏 💪 Thanks for reading!

*I would love to hear your ideas and feedback. Feel free to comment below!*

### ✍️ Written by

Huy Trinh 🔥 🎩 ♥️ ♠️ ♦️ ♣️ 🤓

Software developer | Magic lover

Say Hello 👋 on

✅ [Github](https://github.com/HuyAms)

✅ [LinkedIn](https://www.linkedin.com/in/huydinhtrinh/)

✅ [Medium](https://medium.com/@trnhnhhuy)