loading...
Cover image for What you may not know about the Error Boundary

What you may not know about the Error Boundary

dinhhuyams profile image TrinhDinhHuy ・6 min read

Prerequisite: Basic knowledge about React

I believe that you might know about Error Boundaries BUT do you know how to recover a component from an error? 😌 Do you know how to survive through the pandemic?

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?

import React from 'react'

const App = () => {

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

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

import React from 'react'

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

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

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/>)
    }   
}
Enter fullscreen mode Exit fullscreen mode

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

🤔 Why?

You can learn more about that Dark magic here

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.

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; 
  }
}
Enter fullscreen mode Exit fullscreen mode

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; 
  }
Enter fullscreen mode Exit fullscreen mode

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

import React from 'react'

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

const App = () => {
   return (<Flu/>)
}
Enter fullscreen mode Exit fullscreen mode
<ErrorBoundary>
  <App />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

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 😇

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

⭐ 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.

class ErrorBoundary extends React.Component {

  .....

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

    return this.props.children; 
  }
}
Enter fullscreen mode Exit fullscreen mode

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.


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

<ErrorBoundary FallbackComponent={ErrorFallback}>
  <App />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

⭐ 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

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

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

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.

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

error-recover

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.

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

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

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.

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>
   )
}
Enter fullscreen mode Exit fullscreen mode

reset-keys

🚨 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:

🙏 💪 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

LinkedIn

Medium

Discussion

pic
Editor guide