Introduction
React, a popular JavaScript library for building user interfaces offers a powerful tool called Error Boundaries. These Error Boundaries serve as a safety net for React applications, allowing developers to gracefully catch and manage errors, preventing them from propagating up the component tree and causing application crashes.
Developers can utilize Error Boundaries to present users with informative error messages, log error specifics to aid in debugging, and ensure the application remains resilient even in the face of unforeseen challenges. In the forthcoming sections, we will investigate the critical role of error handling and examine how React Error Boundaries can elevate the user experience by serving as a protective shield for your application.
Steps we'll cover:
- What Are React Error Boundaries?
- Basic Usage of Error Boundaries
- Error Boundary Limitations
- Handling errors using
react-error-boundary
library - Resetting your app after an Error
- FallbackComponent prop
What Are React Error Boundaries?
In JavaScript, we use the try...catch
statement to catch errors.
try {
// code that may throw an error
} catch (error) {
// code to handle the error
}
We put the fragile code inside the try
block, and if an error occurs, we catch it in the catch
block. This fragile code is a code we are unsure that there might be undefined or null occurring and we don't want the whole program stopping, so we enclose them in the try
block. The catch
block is where we handle the error. It is there that we can display a message to the user or log the error to the console.
You will find this try...catch
statement very useful when working with imperative code, and you don't want your users to see their app breaking down in the middle of usage.
However, this approach is not suitable for React applications, as it only works for imperative code. React applications are declarative, meaning they are composed of components that render based on the state of the application. Therefore, we need a different approach for handling errors in React applications.
The concept of Error Boundaries in React was introduced in version 16 of the library. Before the introduction of Error Boundaries, errors in React components were challenging to handle gracefully. When an error occurs within a component, it could potentially corrupt the entire component tree, leading to a broken user interface and a poor user experience.
React Error Boundary is like the try..catch
of React. It is a React component that catches JavaScript errors anywhere in its child component tree, logs those errors, and displays a fallback UI instead of the component tree that crashed.
See a component tree below:
root
is the root component of the component tree. It is the parent of COMP I
and COMP II
. COMP I
is the parent of COMP III
and COMP IV
. COMP III
is the parent of COMP V
. COMP IV
is the parent component of COMP VI
. COMP I
, COMP II
, COMP III
, and COMP IV
are leaf components.
If an error occurs in COMP V
, it will propagate up the component tree to COMP III
, then to COMP I
, and finally to root
. If there is no error boundary in the component tree, the entire component tree will be corrupted, and the application will crash.
However, if there is an error boundary in the component tree, it will catch the error and display a fallback UI instead of the component tree that crashed.
Here, the error boundary is COMP III
. It catches the error that occurred in COMP V
and displays a fallback UI instead of the component tree that crashed. The error and crash do not propagate up the component tree to COMP I
and root
. We see now the benefit of React Error Boundary.
Error Boundaries are React components that implement the componentDidCatch
lifecycle method. This method is invoked after an error has been thrown by a descendant component. It receives two arguments: error
and info
. The error
argument is the error that was thrown, while the info
argument is an object that contains a component stack trace.
Basic Usage of Error Boundaries
In the last section, we said that Error Boundaries are React components that implement the componentDidCatch
lifecycle method. A lifecycle method is a special method in a React Component that is invoked at a particular stage in the lifecycle of the component.
The
componentDidCatch
lifecycle method is invoked after an error has been thrown by a descendant component.
There are different stages in the lifecycle of a component in React. The stages are: initialization
, mounting
, updating
, and unmounting
. The componentDidCatch
lifecycle method is invoked during the updating
stage of the component lifecycle.
This method is invoked after an error has been thrown by a descendant component. It receives two arguments: error
and info
.
- The
error
argument is the error that was thrown. - The
info
argument is an object that contains a component stack trace.
Let's see a simple example of an Error Boundary.
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
console.log(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
There are things we need to learn here. See that in the componentDidCatch
method it receives two arguments as we have known. The error
and info
arguments. We set the hasError
property of the state to true
in the componentDidCatch
method. We also log the error and info to the console. We will see the use of the hasError
property later.
This is a basic usage of an Error Boundary. We will see how to use it in a React application.
Handling Different Types of Errors
In React, we can create components from a function or a class. We call them functional components and class components respectively. React Error Boundaries are only created from class components because they implement the componentDidCatch
lifecycle method. There is no way to implement componentDidCatch
lifecycle method in functional components for now. React can come up with their magic in the future where we can be able to create an Error Boundary in functional components but for now, we can only do that in the class components.
We stated in the last section that a React Error Boundary is created when a class component implements the componentDidCatch
lifecycle method. A class component can also be an Error Boundary if it defines either (or both) of the lifecycle methods static getDerivedStateFromError()
or componentDidCatch()
.
The static getDerivedStateFromError()
lifecycle method is invoked after an error has been thrown by a descendant component. It receives the error that was thrown as an argument and should return a value to update state.
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
The getDerivedStateFromError
is used to render a fallback UI after an error has been thrown by a descendant component. The componentDidCatch
lifecycle method is used to log the error information.
componentDidCatch(error, info) {
// You can also log the error to an error-reporting service
logErrorToMyService(error, info);
}
Example:
import React from "react";
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) {
// You can also log the error to an error-reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
The getDerivedStateFromError
method is called during the "render" phase, so side effects are not permitted. For those use cases, use the componentDidCatch
lifecycle method instead. In the componentDidCatch
method, we can log the error to an error reporting service. We can also use it to perform side effects. We will see how to do that later.
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
The code above is an example of how to use an Error Boundary in a React application. We wrap the component we want to catch errors from in the Error Boundary component. In this case, we wrap the MyComponent
component in the ErrorBoundary
component. If an error occurs in the MyComponent
component, it will be caught by the ErrorBoundary
component.
This is akin to try...catch
in JavaScript:
try {
MyComponent();
} catch (error) {
// code to handle the error
}
Error Boundary Limitations
We have seen throughout this post the beauty of handling errors in React using Error Boundary but the downside is that not all errors can be caught by Error Boundary. Some errors cannot be caught by Error Boundary. We will see them in this section.
Error Boundaries do not catch errors for:
Event handlers
Event handlers inside React components are not known by React, they are functions to be called when an event happens. they are handled by the browser DOM. Therefore, if an error occurs in an event handler, it will not be caught by the Error Boundary.
Asynchronous code
Asynchronous code (e.g. setTimeout
or requestAnimationFrame
callbacks) same thing as Event handlers are not caught by React so the Error Boundary cannot catch them to deal with them.
Server-side rendering
Error boundaries are specific to the client side and won't catch errors that occur on the server or in server-side rendering. Server errors should be handled on the server and communicated to the client.
Errors in Error Boundary component
Errors thrown in the error boundary itself (rather than its children) cannot be handled by the Error boundary, it is a component also, so we should take great care when writing our error boundary component.
Error boundaries can help display a fallback UI, but they don't inherently manage the error state of your application. You need to implement your own error state management and recovery strategies.
Handling errors using react-error-boundary
library
Error handling business in React has been simplified by a great library called react-error-boundary. It is a reusable React error boundary component. Supports all React renderers (including React DOM and React Native).
To use it, we need to install it via npm or yarn:
npm install react-error-boundary
Then we import it in our React application:
import { ErrorBoundary } from "react-error-boundary";
With this library, we do not need to create a React Error Boundary component. the library has done that for us. We only need to use it in our React application. react-error-boundary
exposes an ErrorBoundary component, which supports several props to help you build an error boundary component with no effort.
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<ExampleApplication />
</ErrorBoundary>
You see how simple it is. Just wrap the component you want to catch errors from in the ErrorBoundary component. The fallback
prop is the UI to render when there is an error. It can be a React component or a function that returns a React component.
The ErrorBoundary
component has some useful that we can use to render UI fallback or get some errors.
fallbackRender
This fallbackRender
is a Render prop passed to the ErrorBoundary
. The fallbackRender
prop is a function, it is called when an error occurs and the return value is a JSX that is rendered in place of the misbehaving component.
function fallbackRender({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{ color: "red" }}>{error.message}</pre>
</div>
);
}
<ErrorBoundary fallbackRender={fallbackRender}>
<ExampleApplication />
</ErrorBoundary>;
See that the render prop is called with an object that destructs to error
and resetErrorBoundary
. The error
is the error message details and the resetErrorBoundary
is a function that can be called to reset the error boundary and retry the render.
Resetting your app after an Error
The react-error-boundary
provides us with a prop onReset
which we can use to reset our component when an error is thrown.
function fallbackRender({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{ color: "red" }}>{error.message}</pre>
</div>
);
}
<ErrorBoundary
fallbackRender={fallbackRender}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<ExampleApplication />
</ErrorBoundary>;
The onReset
is called when the resetErrorBoundary
function is called. The resetErrorBoundary
function is passed to the fallbackRender
function as a parameter.
We can now add a button in the JSX returned by the fallbackRender
function to reset the error boundary.
function fallbackRender({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{ color: "red" }}>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
The resetErrorBoundary
function is called when the retry
button is clicked. This will reset the error boundary and retry the render.
You see that the library has made it easy for us to handle errors in our React application. We don't need to create an Error Boundary component, the library has done that for us. We only need to use it in our React application.
FallbackComponent prop
The react-error-boundary
library also provides us with a FallbackComponent
prop. This prop is used to render a fallback UI when an error occurs. It is a React component.
function ErrorFallbackComponent({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{ color: "red" }}>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
<ErrorBoundary FallbackComponent={ErrorFallbackComponent}>
<ExampleApplication />
</ErrorBoundary>;
The ErrorFallbackComponent
is a React component that renders a fallback UI when an error occurs. It receives two props: error
and resetErrorBoundary
. The error
is the error message details and the resetErrorBoundary
is a function that can be called to reset the error boundary and retry the render.