When you are writing a react application you have two ways to handling errors:
- Using try/catch block in each component
- Using React Error Boundary which is only available in class Component :(
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 it using the two approaches stated above.
Try/catch
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 App
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.
React Error Boundary
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
- Class component
- and It must implement
static getDerivedStateFromError()
orcomponentDidCatch()
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 form the content of ErrorHandler
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.
Exceptions to Error handling
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
- Event handlers
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)
Error recovery
This library offers an error recovery feature, that allow 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 which 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 error in React functional components should be a breeze for anyone using the react-error-boundary
library. It provides the following features:
- Fallback components to display incase of error
- Granular capturing of error at component level
- Recovery of error using a function or by resetting the elements causing the component to fail.
Top comments (3)
Nice, thanks for writing an easy to follow tutorial.
Excellent!
Brilliant!