Master React Error Boundaries — how they catch render errors, the difference between getDerivedStateFromError and componentDidCatch, what they do and don't catch, and how to build resilient UIs that survive component crashes.
An Error Boundary is React's circuit breaker. Without one, a JavaScript error thrown during rendering propagates up through every parent component and unmounts the entire application — the screen goes blank. An Error Boundary wraps a subtree and intercepts errors before they escape, showing a fallback UI while keeping the rest of the app alive. Think of it as blast containment: one widget crashes, the page survives.
React's default behaviour when a component throws during rendering: the error propagates up the tree, React unwinds every parent component, and the entire application unmounts. Your users see a blank white screen with nothing in the console except a stack trace.
This is intentional — React would rather show nothing than a broken, half-rendered UI. But "nothing" is a terrible user experience. Error Boundaries let you replace "nothing" with a graceful fallback for just the broken section, while the rest of the app keeps running.
Error Boundaries use two lifecycle methods that have no hooks equivalent — this is the one case in modern React where you must write a class component:
Called during the render phase when a child throws. Its job is purely to update state so the next render shows the fallback UI. It must be a pure function — no side effects.
static getDerivedStateFromError(error) {
// Return a state update — this triggers a re-render with the fallback
return { hasError: true, error };
}
Called during the commit phase after the fallback UI has rendered. This is where you log the error to an external service. Unlike getDerivedStateFromError, side effects are safe here.
componentDidCatch(error, info) {
// info.componentStack shows the component tree that led to the error
logErrorToSentry(error, { componentStack: info.componentStack });
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
logToErrorService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div>Something went wrong.</div>;
}
return this.props.children;
}
}
Error Boundaries catch errors that happen in:
Error Boundaries cannot catch errors from:
// ❌ Error Boundary WON'T catch this
function Button() {
function handleClick() {
throw new Error("oops"); // event handler — use try/catch here
}
return <button onClick={handleClick}>Click</button>;
}
// ✅ Handle event handler errors with try/catch
function Button() {
function handleClick() {
try {
riskyOperation();
} catch (err) {
setError(err.message);
}
}
return <button onClick={handleClick}>Click</button>;
}
Place Error Boundaries at the right level of granularity. Too high (one at the app root) and a broken widget takes down everything visible. Too low (one on every component) and you add unnecessary overhead.
// Route-level: each page is isolated — one page crashing doesn't affect others
<ErrorBoundary fallback={<ErrorPage />}>
<Routes />
</ErrorBoundary>
// Widget-level: a broken chart doesn't break the whole dashboard
<ErrorBoundary fallback={<p>Chart unavailable</p>}>
<RevenueChart />
</ErrorBoundary>
After an error, you can offer the user a way to retry by resetting the error state. The standard pattern is a "Try again" button that clears hasError:
render() {
if (this.state.hasError) {
return (
<div>
<p>Something went wrong.</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
Writing Error Boundary classes is boilerplate. The react-error-boundary library provides a ready-made <ErrorBoundary> component and a useErrorBoundary() hook that lets you programmatically trigger the boundary from inside a functional component (e.g. after a failed async operation):
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
// Wrap any subtree
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)}
onError={(error, info) => logToSentry(error, info)}
onReset={() => refetch()} // called when resetErrorBoundary fires
>
<MyFeature />
</ErrorBoundary>Many developers think Error Boundaries catch all React errors — they only catch errors thrown during rendering, lifecycle methods, and constructors of child components. Errors in event handlers, async callbacks, and setTimeout are invisible to Error Boundaries and need try/catch.
Many developers think you can implement Error Boundaries with hooks — there is no hooks equivalent for getDerivedStateFromError and componentDidCatch. Error Boundaries must be class components. This is the last remaining use case that requires a class component in modern React.
Many developers place one Error Boundary at the app root and consider it done — a root-level boundary only prevents the blank screen; it doesn't provide meaningful isolation. Widget-level boundaries ensure a broken chart doesn't kill a dashboard, a broken sidebar doesn't kill a page.
Many developers think getDerivedStateFromError and componentDidCatch do the same thing — getDerivedStateFromError runs during the render phase and must be pure (its only job is to update state). componentDidCatch runs after the commit phase and is the right place for side effects like logging.
Many developers try to log errors in getDerivedStateFromError — logging is a side effect and getDerivedStateFromError runs in the render phase where side effects are unsafe (React may call it multiple times in concurrent mode). Always log in componentDidCatch.
Many developers think Error Boundaries make async errors safe — a fetch() that rejects after the render phase is not caught by an Error Boundary. For async error handling inside functional components, use the useErrorBoundary() hook from react-error-boundary to programmatically throw errors into the boundary.
Route-level boundaries in SPAs: wrapping each route with an Error Boundary means a crash in the /settings page doesn't break the /dashboard page — users can navigate away and keep working.
Widget isolation in dashboards: analytics dashboards wrap each chart or data card in an Error Boundary so a broken third-party chart library doesn't crash the entire reporting page.
Error logging with Sentry/Datadog: componentDidCatch is the standard integration point for error monitoring services — it receives the full error object and component stack trace for remote logging without the user seeing a broken UI.
React Query integration: when a query throws during rendering, wrapping the component in an Error Boundary with throwOnError: true provides clean fallback UI without manually checking error state in every component.
React Suspense pairing: Error Boundaries and Suspense are always used together when using React.lazy — Suspense handles the loading state, ErrorBoundary handles the failed load (network error, chunk failed to parse).
Retry buttons in production UIs: the reset pattern (clearing hasError on button click, plus calling the onReset callback) lets users recover from transient errors like network blips without a full page reload.
No questions tagged to this topic yet.
Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.