Error Boundaries
An error boundary is a component that catches errors thrown while rendering
its subtree and shows a fallback instead of the broken part. Without one, a
rendering error unmounts the whole app. It is a class component with
static getDerivedStateFromError and/or componentDidCatch: there is no hook
for it, so projects write one class or use the react-error-boundary
package. Errors in event handlers and async code are not caught.
Applies to: React 19.3, react-error-boundary 6.1.
Details
What It Catches
| Caught | Not caught |
|---|---|
| Errors during rendering of any component below | Event handlers (onClick, onSubmit) |
Lifecycle methods and effects (useEffect, useLayoutEffect) | Async code: setTimeout, promise callbacks, await in a handler |
A function passed to startTransition from useTransition or to <form action>, async ones too (React 19) | Server rendering: retried on the client, see below |
A rejected promise read with use | Errors in the boundary itself: they go to the next boundary up |
- Errors that are not caught are handled with
try/catchand state, or moved into the render: store the error in state and throw it there, or callshowBoundaryfromreact-error-boundary. - Server rendering: a boundary does nothing on the server. If a component
inside
<Suspense>throws, React sends the Suspense fallback in the HTML and retries on the client; if it fails again, the nearest boundary catches it. - The search goes up the tree from the component that threw: the nearest boundary shows its fallback, the rest of the app keeps working.
The Two Methods
| Method | Phase | Job |
|---|---|---|
static getDerivedStateFromError(error) | render | Return the state that switches to the fallback; must be pure |
componentDidCatch(error, info) | commit | Side effects: log the error and info.componentStack to a service |
- React 19 logs a caught error once with
console.errorand does not re-throw it.createRoottakesonCaughtError(caught by a boundary) andonUncaughtError(unmounted the app) for app-wide reporting.
Where to Place Them
- Not one for the whole app: any error would replace the entire page. A top-level boundary is the last resort, not the only one.
- Around parts that can fail independently: a route, a widget, a sidebar, a third-party component. The rest of the page stays usable.
- Next to
<Suspense>: Suspense shows a fallback while data loads, the error boundary when loading fails. Data libraries rely on this pair (useSuspenseQueryin TanStack Query). - In the Next.js App Router,
error.tsxis an error boundary around a route segment; see App Router.
Recovery
- A boundary stays in the error state until its state is reset.
- Change its
key: React remounts the boundary and the subtree, for examplekey={pathname}to reset on navigation. - In
react-error-boundary:resetErrorBoundary()from the fallback (a "Try again" button),resetKeys(reset when one of the values changes) andonReset, which runs on every reset, to clear what caused the error.
react-error-boundary
| API | What it does |
|---|---|
<ErrorBoundary fallback={…}> | A static fallback element |
fallbackRender / FallbackComponent | A fallback that receives { error, resetErrorBoundary } |
onError(error, info) | Logging, like componentDidCatch |
resetKeys, onReset | Reset when a value changes; a callback on every reset |
useErrorBoundary() → showBoundary(error) | Send an error from an event handler or async code to the nearest boundary |
withErrorBoundary(Component, props) | The same as a HOC |
The caught value is typed unknown (since version 6.1): anything can be thrown,
not only an Error.
Example
A boundary written by hand:
import { Component } from 'react';
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary fallback={<p>Something went wrong.</p>}>
<Profile />
</ErrorBoundary>;
react-error-boundary: retry and an error from an event handler
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
function Fallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>{error instanceof Error ? error.message : 'Unknown error'}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function SaveButton() {
const { showBoundary } = useErrorBoundary();
async function handleClick() {
try {
await saveDraft();
} catch (error) {
showBoundary(error); // the boundary does not see this error on its own
}
}
return <button onClick={handleClick}>Save</button>;
}
export default function Editor({ draftId }) {
return (
<ErrorBoundary
FallbackComponent={Fallback}
resetKeys={[draftId]} // another draft: reset automatically
onError={(error, info) => report(error, info.componentStack)}
>
<SaveButton />
</ErrorBoundary>
);
}
Follow-up Questions
- Why is there no hook for error boundaries? A boundary must catch an error
thrown while its children render and render something else instead. Function
components have no API for this yet; only the class methods
getDerivedStateFromErrorandcomponentDidCatchcan do it. - How to show the fallback for an error in an event handler?
showBoundaryfromuseErrorBoundary, or run the work instartTransitionfromuseTransition: an error thrown in such a transition reaches the boundary; the standalonestartTransitionimported fromreactis not connected to a boundary. Or store the error in state and throw it during render. - What happens to the state of the components under the boundary? They are unmounted when the fallback is shown; after a reset they mount again with fresh state.
- How do error boundaries relate to Suspense? They are a pair: Suspense handles "still loading", the boundary handles "failed". Both are found by walking up the tree.
- How to report errors in production?
componentDidCatchoronErrorwith a service such as Sentry; the component stack is minified and decoded with source maps.onCaughtErrorandonUncaughtErrorofcreateRootcover the whole app.
Common Mistakes
- Expecting a boundary to catch errors from
onClick,setTimeoutor afetchin a handler. - Throwing code in the same component that renders the boundary:
<ErrorBoundary>{renderChart()}</ErrorBoundary>callsrenderChartduring the parent's render, above the boundary. A boundary catches errors of its descendant components only. - A fallback without a way out: no retry and no reset on navigation, so the user has to reload the page.
See Also
- Class Components and Lifecycle Methods
- Concurrent Rendering — Suspense and transitions.
- Error Handling —
try/catchin JavaScript. - react-error-boundary