JavaScript and React course Β· Module 16: Error Handling and Suspense
react-error-boundary - A Modern Library
In this lesson6
Your class-based bulkhead from the previous lesson works but look at what it's missing: you can't reset it after the module is repaired, it won't react to an error from a click or from fetch, and every project rewrites it from scratch. Creating Error Boundaries from scratch requires writing class components, which may seem archaic in the age of hooks. Fortunately, there's the react-error-boundary library, which greatly simplifies working with error handling. Even the react.dev documentation recommends it as a ready-made alternative to writing your own class.
Installation and Basic Usage
You install the library like any npm package. The command below is for npm, in a yarn project you'd type yarn add react-error-boundary:
1npm install react-error-boundaryAt the time of writing, the current version is 6.x. It works with React 19 (and 18), ships as an ES module and is a client component, so in the Next.js App Router the file using it needs the "use client" directive.
The library provides a ready-made ErrorBoundary component with many useful features. First we define the fallback component, then wrap the station in the boundary and hook up two callbacks:
1import { ErrorBoundary } from 'react-error-boundary';
2
3function ErrorFallback({ error, resetErrorBoundary }) {
4 return (
5 <div className="error-panel">
6 <h2>Module failure!</h2>
7 <p>{error.message}</p>
8 <button onClick={resetErrorBoundary}>
9 Restart module
10 </button>
11 </div>
12 );
13}
14
15function App() {
16 return (
17 <ErrorBoundary
18 FallbackComponent={ErrorFallback}
19 onError={(error, info) => {
20 // Logging to a monitoring service
21 console.error('Logged error:', error);
22 }}
23 onReset={() => {
24 // Reset application state after the error is fixed
25 console.log('Error boundary reset');
26 }}
27 >
28 <SpaceStation />
29 </ErrorBoundary>
30 );
31}onError corresponds to the componentDidCatch method: it receives the error and an object with componentStack. onReset is called when the boundary is cleared, for example after clicking "Restart module". React then tries to render SpaceStation again. One note about version 6: in the TypeScript types error has the type unknown, because you can throw anything, not only an Error object. If you want to extract the message safely, the library exports a helper function getErrorMessage(error).
FallbackComponent vs fallback vs fallbackRender
The library offers three ways to define a fallback UI. They do the same job and differ in convenience and in access to error data.
1. FallbackComponent (Recommended)
You pass a component, and the library renders it with the error and resetErrorBoundary props:
1function MyFallback({ error, resetErrorBoundary }) {
2 return (
3 <div>
4 <p>Error: {error.message}</p>
5 <button onClick={resetErrorBoundary}>Try again</button>
6 </div>
7 );
8}
9
10<ErrorBoundary FallbackComponent={MyFallback}>
11 <App />
12</ErrorBoundary>Such a component can be tested on its own and reused in many boundaries, which is why it's my default choice.
2. fallback (Simple JSX)
When you don't need the error message or a reset button, a ready JSX element is enough:
1<ErrorBoundary fallback={<p>Something went wrong!</p>}>
2 <App />
3</ErrorBoundary>This variant receives no props at all, so the crew will see the message but won't be able to restart the module.
3. fallbackRender (Render Prop)
The third option is a function called with the same data, handy for short, one-off fallbacks:
1<ErrorBoundary
2 fallbackRender={({ error, resetErrorBoundary }) => (
3 <div>
4 <p>{error.message}</p>
5 <button onClick={resetErrorBoundary}>Reset</button>
6 </div>
7 )}
8>
9 <App />
10</ErrorBoundary>The function receives the same object as FallbackComponent, only the form of writing changes.
The useErrorBoundary Hook
One of the library's most powerful tools - the useErrorBoundary hook lets you programmatically trigger errors in an Error Boundary from event handlers and asynchronous code! It returns a showBoundary(error) function that passes the error to the nearest boundary, and resetBoundary(), which clears it:
1import { useErrorBoundary } from 'react-error-boundary';
2
3function DataFetcher() {
4 const { showBoundary } = useErrorBoundary();
5 const [data, setData] = useState(null);
6
7 const fetchData = async () => {
8 try {
9 const response = await fetch('/api/missions');
10 if (!response.ok) {
11 throw new Error('Error fetching mission data');
12 }
13 const result = await response.json();
14 setData(result);
15 } catch (error) {
16 // Pass the error to the nearest Error Boundary!
17 showBoundary(error);
18 }
19 };
20
21 return (
22 <div>
23 <button onClick={fetchData}>Fetch mission data</button>
24 {data && <p>Missions: {data.length}</p>}
25 </div>
26 );
27}The try/catch itself still catches the error, only what we do with it changes: instead of a local message we trigger the same fallback that handles rendering errors. React 19 also offers an alternative: an error thrown in the function passed to startTransition from useTransition also reaches the nearest boundary.
resetKeys - Automatic Reset
You can configure resetKeys - a list of values whose change will automatically reset the Error Boundary. This is useful when the failure depends on the selected data:
1function App() {
2 const [selectedPlanet, setSelectedPlanet] = useState('Mars');
3
4 return (
5 <ErrorBoundary
6 FallbackComponent={ErrorFallback}
7 resetKeys={[selectedPlanet]}
8 onReset={() => {
9 // Cleanup after reset
10 console.log('Boundary reset - new planet:', selectedPlanet);
11 }}
12 >
13 <PlanetDetails planet={selectedPlanet} />
14 </ErrorBoundary>
15 );
16}
17// When the user changes the planet, the Error Boundary will automatically resetGood to know: after a key change, onReset receives an object with the field reason: 'keys', and after a reset from the button, reason: 'imperative-api'. That way you know what triggered the restart.
Nested Error Boundaries
You can nest multiple Error Boundaries for different levels of handling, just like in the previous lesson:
1<ErrorBoundary FallbackComponent={AppCrashScreen}>
2 <Header />
3 <ErrorBoundary FallbackComponent={SectionError}>
4 <MissionControl />
5 </ErrorBoundary>
6 <ErrorBoundary FallbackComponent={SectionError}>
7 <CrewPanel />
8 </ErrorBoundary>
9</ErrorBoundary>If an error occurs in MissionControl, it will be caught by the inner ErrorBoundary. If the inner one itself throws an error, it will be caught by the outer one.
Comparison: Custom Error Boundary vs react-error-boundary
Let's put the two bulkheads side by side:
| Feature | Custom (class) | react-error-boundary |
|---|---|---|
| Reset | Manually implemented | Built-in (resetErrorBoundary) |
| Auto-reset | None | resetKeys |
| Hook | None | useErrorBoundary |
| Logging | Manual | Built-in (onError) |
| Async errors | Doesn't catch | showBoundary() |
| Boilerplate | A lot | Minimal |
The key advantage of the library is the useErrorBoundary hook - it lets you catch errors from event handlers and asynchronous code, which native Error Boundaries can't do. In practice, if you're building a serious production application, react-error-boundary is the industry standard - just like standardized safety systems on spacecraft. Under the hood it's still the same class with getDerivedStateFromError, so everything you learned before still applies. In the following lessons you'll combine it with forms, monitoring and Suspense.
Remember: don't build a bulkhead from scratch on every ship, reach for a proven module and focus on what the crew will see.
Code for this lesson: App.jsx
1import React, { useState } from 'react';
2import './styles.css';
3
4// Simulation of react-error-boundary
5// (in production: import { ErrorBoundary } from 'react-error-boundary')
6
7class ErrorBoundary extends React.Component {
8 constructor(props) {
9 super(props);
10 this.state = { hasError: false, error: null };
11 }
12
13 static getDerivedStateFromError(error) {
14 return { hasError: true, error };
15 }
16
17 componentDidCatch(error, info) {
18 if (this.props.onError) {
19 this.props.onError(error, info);
20 }
21 }
22
23 resetErrorBoundary = () => {
24 if (this.props.onReset) {
25 this.props.onReset();
26 }
27 this.setState({ hasError: false, error: null });
28 };
29
30 render() {
31 if (this.state.hasError) {
32 const { FallbackComponent, fallback, fallbackRender } = this.props;
33 if (FallbackComponent) {
34 return (
35 <FallbackComponent
36 error={this.state.error}
37 resetErrorBoundary={this.resetErrorBoundary}
38 />
39 );
40 }
41 if (fallbackRender) {
42 return fallbackRender({
43 error: this.state.error,
44 resetErrorBoundary: this.resetErrorBoundary,
45 });
46 }
47 if (fallback) return fallback;
48 return <p>Something went wrong</p>;
49 }
50 return this.props.children;
51 }
52}
53
54// FallbackComponent
55function ModuleError({ error, resetErrorBoundary }) {
56 return (
57 <div className="error-card">
58 <h3>Module failure</h3>
59 <p className="error-msg">{error.message}</p>
60 <button onClick={resetErrorBoundary} className="retry-btn">
61 Restart module
62 </button>
63 </div>
64 );
65}
66
67// Unstable component
68function UnstablePanel({ name }) {
69 if (Math.random() < 0.5) {
70 throw new Error(name + ': critical system error!');
71 }
72 return (
73 <div className="panel-ok">
74 <span className="dot green" />
75 {name} - Online
76 </div>
77 );
78}
79
80function StablePanel({ name }) {
81 return (
82 <div className="panel-ok">
83 <span className="dot green" />
84 {name} - Online
85 </div>
86 );
87}
88
89export default function App() {
90 const [key, setKey] = useState(0);
91
92 return (
93 <div className="app">
94 <h1>react-error-boundary</h1>
95 <p className="subtitle">
96 Three ways to define fallback UI
97 </p>
98 <button onClick={() => setKey(k => k + 1)} className="reset-all">
99 Reset everything
100 </button>
101
102 <div className="panels" key={key}>
103 <div className="panel-section">
104 <h2>FallbackComponent</h2>
105 <ErrorBoundary
106 FallbackComponent={ModuleError}
107 onError={(err) => console.log('Logged:', err.message)}
108 onReset={() => console.log('Reset!')}
109 >
110 <UnstablePanel name="Navigation" />
111 </ErrorBoundary>
112 </div>
113
114 <div className="panel-section">
115 <h2>fallbackRender</h2>
116 <ErrorBoundary
117 fallbackRender={({ error, resetErrorBoundary }) => (
118 <div className="error-card">
119 <p>{error.message}</p>
120 <button onClick={resetErrorBoundary} className="retry-btn">
121 Fix
122 </button>
123 </div>
124 )}
125 >
126 <UnstablePanel name="Communication" />
127 </ErrorBoundary>
128 </div>
129
130 <div className="panel-section">
131 <h2>fallback (prosty JSX)</h2>
132 <ErrorBoundary fallback={<p className="simple-err">Module offline</p>}>
133 <UnstablePanel name="Sensors" />
134 </ErrorBoundary>
135 </div>
136
137 <div className="panel-section">
138 <h2>Stable module</h2>
139 <ErrorBoundary FallbackComponent={ModuleError}>
140 <StablePanel name="Life support" />
141 </ErrorBoundary>
142 </div>
143 </div>
144 </div>
145 );
146}Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What props does FallbackComponent receive in react-error-boundary?
2. What is the purpose of the useErrorBoundary hook from the react-error-boundary library?
Hands-on tasks in the game
- Horizontal ordering
Arrange the correct syntax for using ErrorBoundary with FallbackComponent:
- Code editor
Implement async error handling
- Click in order
Click elements in order for ErrorBoundary syntax with resetting:
- Click in order
Click elements in order for using useErrorBoundary with fetch: