In the ground since Fri Oct 01 2021
Last watered inFri Oct 01 2021
Handling Errors in React
These are the most common types of errors that can occur in a React application:
- Render Errors: .
- API Errors / Data Manipulation Errors:
- Route Errors:
Let's start by understanding how to handle them.
Render Errors
By default, if out application throws an error during rendering or effects, did-mount/did-update. React will remove its UI from the screen. The <body> tag will be almost empty. Our UI gets removed!

These are errors that occur when we:
- Access a property of an undefined object. A) when're we creating variables before the block of a component B) when we're declaring our UI inside the return block of a component.
e.g. Let's simulate an user which doesn't have an street nor a city.
1const ComponentThatWillBreak = ({ user }) => {
2 const userStreet = user.address.street; // A)
3
4 return (
5 <div>
6 <p>
7 User Street: <span>{userStreet}</span>
8 </p>
9 <p>
10 User City
11 <span>{user.address.city}</span> // B)
12 </p>
13 </div>
14 );
15};
16
17<ComponentThatWillBreak user={{ name: "Mario" }} />;
- Our screen will break

- The error will be thrown in the console only. The user won't know what's happening.

- The whole application will be unmounted.

To prevent this, we can use an approach called "Error Boundary".
Error Boundary
An error boundary is a special component that lets you display some fallback
UI instead of the part that crashed—for example, an error message.
Currently, the official doc only points to solution using React Class Components. To use a function component Error Boundary, we can use ErrorBoundary or useErrorBoundary hook from react-error-boundary library.
How does it work?
It focus on wrapping the components that might throw an error.
If it happens, the error is caught by the nearest error boundary. The error boundary will then display a fallback UI.
What does it not cover?
Before continuing, if anything does't work as expected, we need to remember that some situations are not covered by error boundaries:
- Error boundaries do not catch errors for:
- Event handlers
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- The ErrorBoundary can't be set inside the component that might throw an error. It must be set outside of it!! For god's sake, don't forget this! It will save you a lot of time!
- Errors thrown in the error boundary itself (rather than its children)
These cases will be covered in the next sections. But furst, let's ignore them!
Let's Start
We'll create an Fallback UI to informe the user about the error.
We'll wrap ComponentThatWillBreak with ErrorBoundary.
1import { ErrorBoundary } from "react-error-boundary";
2
3const FallbackComponent = ({ error }: { error: Error }) => {
4 return (
5 <div role="alert">
6 <p>Ops! Something went wrong. The reason is:</p>
7 <pre style={{ color: "red" }}>{error.message}</pre>
8 </div>
9 );
10};
11
12const ComponentThatWillBreak = ({ user }) => {
13 const userStreet = user.address.street; // A)
14
15 return (
16 <div>
17 <p>
18 User Street: <span>{userStreet}</span>
19 </p>
20 <p>
21 User City
22 <span>{user.address.city}</span> // B)
23 </p>
24 </div>
25 );
26};
27
28const App = () => {
29 return (
30 <ErrorBoundary FallbackComponent={FallbackComponent}>
31 <ComponentThatWillBreak user={{ name: "Mario" }} />
32 </ErrorBoundary>;
33 )
34}
- Our screen didn't break!
- We can show an generic message and we can even grab/show/treat the error.

- The application didn't get unmounted. Check this out! Our HTML is there!

Way Better! But...
Real world applications are complex and grow fast. They will have nested Components. ErrorBoundary will look for errors in a Parent Component. If everything's fine, it will keep going down the tree to check if nested/children Components are throwing errors.
1import { ErrorBoundary } from "react-error-boundary";
2
3const FallbackComponent = ...
4
5
6const ComponentThatWillBreak = ({ user }) => {
7 const userStreet = user.address.street;
8
9 return (
10 <div>
11 <p>
12 User Street
13 <span>{userStreet}</span>
14 </p>
15 <p>
16 User City
17 <span>{user.address.city}</span>
18 </p>
19
20 <MyChildComponent user={user} />
21 </div>
22 );
23};
24
25const MyChildComponent = ({ user }) => {
26 const userLastLogin = user.lastLogin.date; // A)
27
28 return (
29 <div>
30 <p>
31 Last login at: <span>{userLastLogin}</span>
32 </p>
33 </div>
34 );
35};
36
37const App = () => {
38 return (
39 <ErrorBoundary FallbackComponent={FallbackComponent}>
40 <ComponentThatWillBreak user={{
41 name: "Mario",
42 }} />
43 </ErrorBoundary>;
44 )
45}
46
Now the app have two errors:
| ComponentThatWillBreak -> "user.address is undefined"
| - MyChildComponent -> "user.lastLogin is undefined"
ErrorBoundary will catch the first error only and display it.
Only that.
If we fix the first error, the second one will be displayed.

Nice! It's safe to assume that we can create a "Global" Error Boundary and place it at the Top Level of our App. Fallback a generic message to all possible errors that might happen.
It's not perfect, but at least our App doesn't show an Empty screen and we can in the future use the FallbackComponent to log the error and send it to a monitoring tool.
Nested Error Levels aka "Being more specific"
We can definetly have one ErrorBoundary nested for the whole App. But we can also have multiple ErrorBoundary for specific Components. It's up to us to decide which one to use. It's a matter of context and complexity of the App
Let's create an approach:
Mental Model
- A Generic Fallback Component for the whole App. This is the Level 1 Better safe than sorry!
- Lots of Specific FallBack Components for specific Components. This is the Level 2
Level 1 It's a global treatment. If in the future, any Component is added in the App and it's not wrapped in a Error Boundary, we can rest assured that if it breaks, we'll know and we'll display a properly Fallback UI. A Generic one? 100%. But still better tha
n a blank page!
1const GlobalFallbackUI = () => {
2 return (
3 <div role="alert" style={{ color: "red" }}>
4 <p>Ops! Something went wrong</p>
5 </div>
6 );
7};
8
9const GlobalFallback = ({ children }: { children: React.ReactNode }) => {
10 return (
11 <ErrorBoundary FallbackComponent={GlobalFallbackUI}>
12 {children}
13 </ErrorBoundary>
14 );
15};
Level 2 It's a more specific treatment. We can wrap a specific Component that we know that might break. We can display a more specific Fallback UI. We can even grab the error and treat it.
1const ChildFallbackUI = ({ error }: { error: Error }) => {
2 console.log("calling mom and dad because:", error);
3
4 return (
5 <div role="alert" style={{ color: "red" }}>
6 <p>I am a Child but I can take care of my self</p>
7 </div>
8 );
9};
10
11const ChildFallback = ({ children }: { children: React.ReactNode }) => {
12 return (
13 <ErrorBoundary FallbackComponent={ChildFallbackUI}>
14 {children}
15 </ErrorBoundary>
16 );
17};
Now it's time to use them:
1const ComponentThatWillBreak = ({ user }) => {
2 const userStreet = user.address.street;
3
4 return (
5 <div>
6 <p>
7 User Street:
8 <span>{userStreet}</span>
9 </p>
10 <p>
11 User City:
12 <span>{user.address.city}</span>
13 </p>
14
15 <ChildFallback> {/* This a the Level 2 usage */}
16 <MyChildComponent user={user} />
17 </ChildFallback>
18 </div>
19 );
20};
21
22const App = () => {
23 return (
24 <GlobalFallback>{/* This a the Level 1 usage */}
25 <ComponentThatWillBreak user={{
26 name: "Mario",
27 }} />
28 </GlobalFallback>;
29 )
30}
31
When an error is caught by the GlobalFallback:
1
2const App = () => {
3 return (
4 <GlobalFallback>{/* This a the Level 1 usage */}
5 <ComponentThatWillBreak user={{
6 name: "Mario",
7 // We don't have address nor city
8 }} />
9 </GlobalFallback>;
10 )
11}
12

When an error is caught by the ChildFallback:
1
2const App = () => {
3 return (
4 <GlobalFallback>{/* This a the Level 1 usage */}
5 <ComponentThatWillBreak user={{
6 name: "Mario",
7 address: {
8 city: 'São Paulo',
9 street: 'Rua das Flores',
10 },
11 // We don't have lasstLogin
12 }} />
13 </GlobalFallback>;
14 )
15}

Cool! But writting two Components SomethingFallback SomethingFallbackUI to all Components of a complex App will add complexity to the code.
We can use Higher Order Components to wrap the Components that might break. A HOC is a function that takes a Component, alter the given Component, in any matter and returns a new Component. We can use it to wrap the Components that might break.
1
2const MyChildComponent: React.FC<{ user: any }> = withErrorBoundary(
3 ({ user }) => {
4 const userLastLogin = user.lastLogin.date; // A)
5
6 return (
7 <p>
8 Last login at: <span>{userLastLogin}</span>
9 </p>
10 );
11 },
12 { FallbackComponent: ChildFallbackUI }
13);
14
15const ComponentThatWillBreak = ... // it stays the same
16
17const App = withErrorBoundary(() => {
18 return (
19 <ComponentThatWillBreak
20 user={{
21 name: "Mario",
22 address: {
23 city: "São Paulo",
24 street: "Rua das Flores",
25 },
26 // We don't have lasstLogin
27 }}
28 />
29 );
30}, { FallbackComponent: GlobalFallbackUI });
31
Retry Rendering
We can forçe a re-render after an error. It make sense if we're dealing with a fetch that failed or if we have a strategy where we would fix the cause of the error.
We have two options. By calling a hook useErrorBoundary or by using a prop onReset in the ErrorBoundary component.
Calling the hook inside the Component which we want to recover for
1const ChildFallbackUI = () => {
2 const { resetBoundary } = useErrorBoundary();
3
4 return (
5 <div role="alert" style={{ color: "red" }}>
6 <p>I am a Child but I can take care of my self</p>
7 <button onClick={() => resetBoundary()}>reset</button>
8 </div>
9 );
10};
Calling onReset
1const MyChildComponent: React.FC<{ user: any }> = withErrorBoundary(
2 ({ user }) => {
3 ...
4 },
5 {
6 FallbackComponent: ChildFallbackUI,
7 onReset: (details) => {
8 console.log('details:', details);
9 },
10 }
11);
Logging Errors
In real-world App we might want to store all errors using a monitoring tool. We can use the onError prop in the ErrorBoundary component to log the error.
1withErrorBoundary(
2 () => {
3 return ( ...);
4 },
5 {
6 FallbackComponent: GlobalFallbackUI,
7 onError: logError,
8 }
9);
10
11const logError = (error: Error, info: { componentStack: string }) => {
12 Sentry.captureException(error);
13};
Writting Roadmap
-> Write an error on event handlers. A click will do!
-> Write an Error simulating a fetch that fails
API Errors / Data Manipulation Errors
Route Errors
Create Root
https://react.dev/reference/react-dom/client/createRoot#displaying-error-boundary-errors
Recoverable errors
https://react.dev/reference/react-dom/client/createRoot#displaying-a-dialog-for-recoverable-errors