How do error boundaries work in React?
The Short Answer
Error boundaries are React class components that catch JavaScript errors in their child component tree during rendering, lifecycle methods, and constructors. They prevent a single component's crash from taking down the entire app — instead, they display a fallback UI. They work like a try...catch block but for React's component tree. Error boundaries don't catch errors in event handlers, async code, or server-side rendering.
The Problem They Solve
Before error boundaries, a runtime error in any component would corrupt React's internal state and cause the entire app to unmount — showing a blank white screen. There was no way to gracefully recover. Error boundaries let you isolate failures so that a broken widget, chart, or sidebar doesn't crash the whole page.
Circuit Breaker
Error boundaries work like circuit breakers in your house. When one circuit overloads (a component throws), the breaker trips and cuts power to that circuit only — the rest of the house keeps running. Without breakers, one fault would take down everything.
Implementing an Error Boundary
An error boundary is a class component that implements either static getDerivedStateFromError() (to render a fallback UI) or componentDidCatch() (to log the error), or both. There's no hook equivalent — error boundaries must be class components. Most apps create one reusable error boundary and wrap it around different sections.
import { Component, ErrorInfo, ReactNode } from 'react';
type Props = {
children: ReactNode;
fallback?: ReactNode;
};
type State = {
hasError: boolean;
error: Error | null;
};
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
// Called during rendering — update state to show fallback
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
// Called after error is caught — use for logging
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Error boundary caught:', error);
console.error('Component stack:', errorInfo.componentStack);
// Send to error reporting service (Sentry, DataDog, etc.)
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div role="alert">
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
Using Error Boundaries
Wrap error boundaries around sections of your app that should fail independently. The granularity depends on your UX — you might wrap the entire app (to prevent white screens), individual widgets (so one broken chart doesn't hide the dashboard), or route-level boundaries (so navigation still works if a page crashes).
function App() {
return (
<ErrorBoundary fallback={<p>The app crashed. Please refresh.</p>}>
<Header />
<main>
{/* Each section fails independently */}
<ErrorBoundary fallback={<p>Chart failed to load</p>}>
<AnalyticsChart />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Feed unavailable</p>}>
<ActivityFeed />
</ErrorBoundary>
</main>
<Footer />
</ErrorBoundary>
);
}
// If AnalyticsChart throws, only its section shows the fallback.
// ActivityFeed, Header, and Footer continue working normally.
Nesting error boundaries gives you layered protection. The inner boundary catches errors first; if it doesn't exist or re-throws, the error bubbles up to the next boundary in the tree.
What Error Boundaries Don't Catch
Error boundaries only catch errors during React's rendering phase. Several categories of errors slip through and need separate handling. This is a common interview follow-up — knowing the limitations is as important as knowing the feature.
Errors NOT caught by error boundaries
- ❌Event handlers (use try...catch inside the handler)
- ❌Async code (promises, setTimeout, requestAnimationFrame)
- ❌Server-side rendering (SSR)
- ❌Errors thrown in the error boundary itself
function BuggyButton() {
function handleClick() {
// ❌ Error boundary won't catch this — it's in an event handler
throw new Error('Clicked!');
}
// ✅ Handle event errors with try...catch
function handleClickSafe() {
try {
riskyOperation();
} catch (error) {
// Show error UI via state, or report to service
setError(error);
}
}
return <button onClick={handleClickSafe}>Click</button>;
}
Recovery and Reset
A common pattern is adding a reset mechanism so users can retry after an error. You can expose a reset function that clears the error state, or use a key prop to force React to remount the boundary (and its children) from scratch.
class ResettableErrorBoundary extends Component<Props, State> {
// ... same as before
resetError = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div role="alert">
<p>Something went wrong.</p>
<button onClick={this.resetError}>Try again</button>
</div>
);
}
return this.props.children;
}
}
// Or use key to force remount:
// <ErrorBoundary key={resetKey}>
// <ProblematicComponent />
// </ErrorBoundary>
// Changing resetKey unmounts and remounts the boundary
Why Interviewers Ask This
This question tests your understanding of React's error handling model and production-readiness thinking. Interviewers want to see that you know how to prevent white-screen crashes, understand the class component requirement (no hook equivalent), can explain what errors are and aren't caught, and think about error recovery UX. It also shows whether you build resilient applications or just happy-path code.
Quick Revision Cheat Sheet
What they catch: Errors during rendering, lifecycle methods, and constructors in child tree
What they don't catch: Event handlers, async code, SSR, errors in the boundary itself
Key methods: getDerivedStateFromError (render fallback) + componentDidCatch (log error)
Must be class component: No hook equivalent exists (as of React 19)
Granularity: Wrap at app level, route level, and widget level for layered protection
Recovery: Reset state with a retry button or change the boundary's key prop