Error boundaries were introduced in React 16 as a way to catch JavaScript errors in your React components.
Error boundaries are React components that catch 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.
However, error boundaries do not handle errors in:
setTimeout
or requestAnimationFrame
callbacks),So basically, error boundaries only handle errors in the parts of our code that involve React.
A simple error boundary component would look like this:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Then we can use this component anywhere in our app:
<ErrorBoundary>
<App />
</ErrorBoundary>
The key point in the previous example is the getDerivedStateFromError
. This lifecycle method is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as a parameter and should return a value to update state.
So when any component inside the app returns an error, error boundary’s getDerivedStateFromError
will trigger and set the hasError
state to true. The render method will then render the fallback UI instead of the children.
If you are using issue tracking tools in your project, ErrorBoundary
is all you need via the componentDidCatch
lifecycle method. This lifecycle is invoked after an error has been thrown by a descendant component. It receives two parameters:
So let’s see it in action. Adding componentDidCatch
to our ErrorBoundary
:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
Sentry.withScope((scope) => {
scope.setContext(..., {
error,
info,
});
Sentry.captureException(error);
});
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
You may think that setting state can be done in componentDidCatch
as well but you shouldn’t. Here is a note from official docs about why:
“In the event of an error, you can render a fallback UI with componentDidCatch()
by calling setState
, but this will be deprecated in a future release. Use static getDerivedStateFromError()
to handle fallback rendering instead.”
So with this final version of our ErrorBoundary
we are able to catch any error in the application and report them to the issue tracking tool if we put our ErrorBoundary
component to root level of the app.
When developing React applications, a page is typically composed of several components in a tree that can escalate quickly. For that reason, if there’s an error in the application, it’s commonly lost in the hierarchy of components.
Error boundaries can help you by raising the red flag on the exact node that experienced the flaw.
TypeScript adds additional syntax to JavaScript to support a tighter integration with your editor so you can catch errors early. If used well, TypeScript can help you eliminate any errors in the app.
Having said that, it doesn’t mean that you don’t need to have an ErrorBoundary. It is still possible to miss things with TypeScript, especially when you generate manually or get an unexpected response from the backend that may cause a page break even if you used TypeScript as expected. So in that case, it is better to show your custom fallback UI to the user instead of a blank page!
If you are using Sentry, it already has its own ErrorBoundary component. You may want to use it or check if your tracking tool has one. Regardless of how you do it, ErrorBoundary has your back.