When you are writing a react application, you have two ways to handle errors:
import * as React from 'react'
import ReactDOM from 'react-dom'
function City({name}) {
return <div>Hello, visit {name.toUpperCase()}</div>
}
function Country({capital}) {
return <div>Hello, visit {capital.toUpperCase()}</div>
}
function App() {
return (
<div>
<Country />
<City />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
The above piece of code would end up showing you an error page when you run it in Development or a blank screen in Production. Obviously, the error we created in the code above could have certainly been handled with PropTypes or TypeScript. However, we are aware runtime error happens all the time, and we are going to deal with them using the two approaches stated above.
import * as React from 'react'
import ReactDOM from 'react-dom'
function ErrorHandler({error}) {
return (
<div role="alert">
<p>An error occurred:</p>
<pre>{error.message}</pre>
</div>
)
}
function City({name}) {
try {
return <div>Hello, visit {name.toUpperCase()}</div>
} catch (error) {
return <ErrorHandler error={error} />
}
}
function Country({capital}) {
try {
return <div>Hello, visit {capital.toUpperCase()}</div>
} catch (error) {
return <ErrorHandler error={error} />
}
}
function App() {
return (
<div>
<Country />
<City />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
This approach requires us to define an ErrorHandler component to display in case an error occurs, and we wrap each component returned element in the try/catch block.
This seems ok, but repetitive. What if we want the parent component to handle the error catching for us. Wrapping the parent component in a try/catch block will not work, due to the nature of how React calls functions. That is when React Error Boundary comes in.
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
As at React 17.0.2, Error Boundary works only in
static getDerivedStateFromError()
or componentDidCatch()
In order to use Error Boundary in Functional Component, I use react-error-boundary.
import * as React from 'react'
import ReactDOM from 'react-dom'
import {ErrorBoundary} from 'react-error-boundary'
function ErrorHandler({error}) {
return (
<div role="alert">
<p>An error occurred:</p>
<pre>{error.message}</pre>
</div>
)
}
function City({name}) {
return <div>Hello, visit {name.toUpperCase()}</div>
}
function Country({capital}) {
return <div>Hello, visit {capital.toUpperCase()}</div>
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorHandler}>
<Country />
<City />
</ErrorBoundary>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
When we run this application, we will get a nice error display from the content of component.
React error boundary catches any error from the components below them in the tree. This is really handy and useful because we need not declare a separate try/catch for each component because the wrapping component(ErrorBoundary) takes care of that and display the component of the FallbackComponent
provided.
Because react-error-boundary uses react error boundary in the background, there are a few exceptions to the errors that can be handled.
These errors are not handled by react-error-boundary
This library offers an error recovery feature that allows you to reset the state and bring back the components to a working point. Let's use this example from the react-error-boundary npmjs page.
function ErrorFallback({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
function Bomb() {
throw new Error('💥 CABOOM 💥')
}
function App() {
const [explode, setExplode] = React.useState(false)
return (
<div>
<button onClick={() => setExplode(e => !e)}>toggle explode</button>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setExplode(false)}
resetKeys={[explode]}
>
{explode ? <Bomb /> : null}
</ErrorBoundary>
</div>
)
}
The ErrorBoundary
component accepts two other props to help recover from a state of error. The first prop onReset
receives a function that will be triggered when resetErrorBoundary
of the FallbackComponent
is called. The onReset
function is used to reset the state and perform any cleanup that will bring the component to a working state.
The other prop of ErrorBoundary
is resetKeys
, it accepts an array of elements that will be checked when an error has been caught. In case any of these elements changes, the ErrorBoundary
will reset the state and re-render the component.
Handling errors in React functional components should be a breeze for anyone using the react-error-boundary
library. It provides the following features: