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>) 🤧
}
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/>)
}
}
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/>)
}
}
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;
}
}
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
import React from 'react'
const Flu = () => (<p>It's just a flu. No need ${vaccine.toUpperCase()}</p>)
const App = () => {
return (<Flu/>)
}
<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 😇
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.
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.
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
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
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>
)
}
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>
)
}
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.
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>
)
}
🚨 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
✅ Medium
Top comments (1)
Nice article.