Error Boundaries
Class error boundaries catch render errors in the tree, show fallback UI, and what they do not catch: events, async, own render.
- react
- error-boundaries
A child throws during render. Without an error boundary, React unmounts the whole tree down to the root and you get a blank page or the framework error overlay. Error boundaries are class components that catch those errors in their subtree, log them, and render fallback UI so the rest of the app survives.
They are still class-only (getDerivedStateFromError / componentDidCatch). There is no first-party hook for this in core React — libraries rethrow into a boundary to simulate one.
Docs: Error Boundaries.
What they catch (and what they do not)
| Caught | Not caught |
|---|---|
| Errors in render of children | Event handlers (onClick) |
| Lifecycle methods of children | Async code (setTimeout, promises) after render |
| Constructors of child class components | Server-only failures outside the client tree |
| Errors in components below the boundary | The boundary’s own render (need a parent boundary) |
For event handlers and async, use try/catch locally and set error state — or deliberately throw during a later render after storing an error so a boundary can handle it.
Minimal boundary
import { Component } from 'react';
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, info) {
console.error('UI crash', error, info.componentStack);
// report to your error service
}
render() {
if (this.state.error) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<button type="button" onClick={() => this.setState({ error: null })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
);
}
Place boundaries around independent product surfaces (sidebar vs main, widget vs page), not only at the root. Root-only means one bad chart kills the entire shell.
Recovery patterns
Remount with key so child state resets on retry:
function Panel() {
const [resetKey, setResetKey] = useState(0);
return (
<ErrorBoundary key={resetKey}>
<RiskyWidget onGiveUp={() => setResetKey((k) => k + 1)} />
</ErrorBoundary>
);
}
Framework UI: In Next.js App Router, error.tsx is a client boundary for a route segment — same idea as a class boundary, with a file convention. Pair with loading UI.
Data libraries: Network failures are not render throws unless you throw them. React Query error states sit beside boundaries by default.
SSR / RSC: Server render errors need server logging and route-level error UI. Client boundaries only protect client render after hydration.
Footguns
- Expecting
onClickthrows to be caught — wrap the handler. - Boundary too high → huge fallback, lost app chrome.
- Logging only in
getDerivedStateFromError— keep that method pure; side effects belong incomponentDidCatch. - Showing raw
error.messageto end users — often internal; prefer generic copy plus a support id.
Interview out-loud
“Error boundaries are class components that implement getDerivedStateFromError and componentDidCatch. They catch render and lifecycle errors below them and show fallback UI. They do not catch event handlers, async, or their own errors. In Next App Router, error.tsx wraps a segment similarly on the client.”
Related on this site
Further reading
Edge cases worth rehearsing
Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.
Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.
When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.
Related guides
- Accessibility Patterns in ReactPractical React a11y: labels, focus management, keyboard, live regions, and composition patterns that stay accessible.
- Avoid Prop Drilling with CompositionStop threading props through intermediates: children slots, inversion of control, and when context is the right escape hatch.
- Batching State UpdatesHow React 18+ batches setState in events, timeouts, and promises: when updates flush and why double setState still works.
- Children Prop PatternsUsing children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.
- Client Component BoundariesWhere to put use client: push interactivity to leaves, serializable props, and children as server slots.