Suggest an editImprove this articleRefine the answer for “Error boundaries in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Error boundaries** are class components that catch JavaScript errors in their child component tree during rendering and show a fallback UI instead of crashing the app. ```jsx class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } render() { if (this.state.hasError) return <h1>Something broke!</h1>; return this.props.children; } } ``` **Key point:** only class components can be error boundaries. Event handlers and async code need `try/catch`.Shown above the full answer for quick recall.Answer (EN)Image**Error boundaries** are React class components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors, then show a fallback UI while logging the error. ## Theory ### TL;DR - Think of it like a circuit breaker in a power grid: one short-circuit trips the breaker, but the rest of the grid stays on. - Without boundaries, one bad component unmounts the entire React tree. With them, only that subtree fails. - `getDerivedStateFromError` updates state to trigger the fallback (sync, during render). `componentDidCatch` logs the error (post-render, for side effects). - Boundaries only catch render-phase errors in descendants. Event handlers and async code need `try/catch`. - Wrap any subtree that can fail independently: third-party widgets, dashboard panels, lazy routes. ### Quick example ```jsx class ErrorBoundary extends React.Component { state = { hasError: false }; // Runs during render - returns state update for fallback static getDerivedStateFromError(error) { return { hasError: true }; } // Runs after commit - good place for Sentry or console logging componentDidCatch(error, info) { console.error('Caught:', error, info.componentStack); } render() { if (this.state.hasError) return <h1>Something broke!</h1>; return this.props.children; } } // Wrap the component that might throw <ErrorBoundary> <BuggyCounter /> {/* throws during render */} </ErrorBoundary> // Result: "Something broke!" instead of a blank screen ``` `getDerivedStateFromError` fires during the render pass so the fallback appears immediately. `componentDidCatch` fires after React commits the fallback to the DOM. ### Key difference Before React 16, a rendering error in any component left the DOM in a broken state with no clean recovery path. React 16 introduced fiber architecture with explicit boundary support: when a component throws during render, React walks up the fiber tree looking for the nearest class component with `getDerivedStateFromError`. That component re-renders with the fallback. Sibling subtrees are untouched. This is pure React fiber traversal, no browser APIs involved. ### When to use - **Third-party widgets or embeds**: wrap each one independently so a vendor script failure does not take down your whole page. - **Dashboard panels**: if one chart's API returns garbage during render, the other panels keep working. - **Lazy-loaded routes**: a failed dynamic import won't blank the entire app. - **App-level fallback**: one global boundary as a last resort, showing a "reload" button and logging to Sentry. - **Not here**: event handlers, `setTimeout`, `fetch` calls. Use `try/catch` or `.catch()` for those. ### How React processes errors internally React's reconciler tracks errors during the render phase. When a component throws, the error bubbles up through fiber nodes until it hits a class component that has `getDerivedStateFromError`. That method is static and synchronous, so React gets the new state immediately and re-renders the fallback in the same pass. After the fallback is committed to the DOM, `componentDidCatch` runs for side effects like logging or sending data to Sentry. One thing I always remind teammates: if the boundary's own `render` throws, that error is not caught by itself. It bubbles to the next boundary up the tree. Keep fallback render logic as simple as possible. ### Common mistakes **1. Expecting event handler errors to be caught** ```jsx // NOT caught - event handlers run outside React's render cycle <ErrorBoundary> <button onClick={() => { throw new Error('boom'); }}>Click</button> </ErrorBoundary> ``` Use `try/catch` inside the handler itself. **2. Skipping `getDerivedStateFromError` and only using `componentDidCatch`** ```jsx // Wrong - setState here queues another render cycle, fallback shows late componentDidCatch(error, info) { this.setState({ hasError: true }); } ``` Add `static getDerivedStateFromError` so the fallback renders in the same pass as the error. **3. Writing a boundary as a functional component** ```jsx // Won't work function BadBoundary({ children }) { const [hasError, setHasError] = useState(false); // no lifecycle to intercept render errors } ``` Only class components can be error boundaries. Use the `react-error-boundary` library if you want a functional-component API. **4. Complex logic inside the boundary's own render** If `render` in the boundary itself throws, that error is not caught at this level. It goes to the next boundary up. Keep fallback JSX static: no data fetching, no conditions beyond the `hasError` flag. ### Real-world usage - **Create React App**: wraps root `<App>` in an `ErrorBoundary` by default since React 16.13. - **Next.js**: `_error.js` (pages router) handles app-level client errors; the app router uses `error.js` per route segment. - **Sentry**: ships `<Sentry.ErrorBoundary>` with a built-in `fallback` prop and automatic error capture. - **Redux Toolkit**: teams wrap lazy-loaded feature slices in individual boundaries. - **Storybook**: per-story boundaries prevent one broken story from killing the whole dev environment. ### Follow-up questions **Q:** Which phases do error boundaries catch errors in? **A:** Render phase, lifecycle methods, and constructors of descendant components. Not event handlers, async code (`setTimeout`, `Promise`), SSR, or the boundary's own render method. **Q:** What is the difference between `getDerivedStateFromError` and `componentDidCatch`? **A:** `getDerivedStateFromError` is static and synchronous. It runs during the render pass and returns new state so the fallback appears immediately. `componentDidCatch` runs after the commit phase and is for side effects: logging, sending to Sentry, updating metrics. **Q:** How do you reset an error boundary after it fires? **A:** Call `setState({ hasError: false })` from a retry button inside the fallback UI. That causes the boundary to re-render its children from scratch. Some teams also watch for prop changes in `componentDidUpdate` to auto-reset. **Q:** Is there a hook-based equivalent? **A:** No official hook exists. Use `react-error-boundary`, which wraps a class boundary and exposes a clean functional API, including `useErrorBoundary` for manually triggering boundaries from async code. **Q:** In concurrent React 18, how do transitions interact with error boundaries? **A:** Errors inside `startTransition` unwind to the nearest boundary without disrupting the committed UI. React can retry the transition separately. The fiber unwinding mechanism means no "zombie" children remain attached to the tree. Worth mentioning this in senior interviews. ## Examples ### Basic: catching a render error ```jsx class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, info) { console.error(error, info.componentStack); } render() { if (this.state.hasError) return <p>Failed to load this section.</p>; return this.props.children; } } function BuggyComponent() { throw new Error('render crash'); // throws on every render } <div> <ErrorBoundary> <BuggyComponent /> {/* shows fallback */} </ErrorBoundary> <p>This paragraph still renders fine.</p> </div> ``` The fallback shows inside the boundary. The sibling paragraph is unaffected because boundaries isolate failures to their own subtree. ### Intermediate: production dashboard with retry and Sentry ```jsx class ChartPanelBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { Sentry.captureException(error, { extra: errorInfo }); } render() { if (this.state.hasError) { return ( <div className="error-panel"> <h3>Chart failed to load</h3> <button onClick={() => this.setState({ hasError: false, error: null })} > Retry </button> </div> ); } return this.props.children; } } function Dashboard() { return ( <div> <ChartPanelBoundary> <RevenueChart dataUrl="/api/revenue" /> </ChartPanelBoundary> <ChartPanelBoundary> <UsersChart dataUrl="/api/users" /> </ChartPanelBoundary> </div> ); } // If RevenueChart crashes, UsersChart keeps working // Retry button sets hasError: false and re-renders children from scratch ``` Each panel gets its own boundary. The retry button calls `setState` to clear `hasError`, which causes the boundary to re-render its children fresh. ### Advanced: what boundaries do not catch ```jsx // 1. Async error - NOT caught by any boundary function AsyncProblem() { useEffect(() => { setTimeout(() => { throw new Error('async boom'); // boundary misses this }, 1000); }, []); return <p>Loaded</p>; } // 2. Event handler error - also NOT caught function ClickProblem() { return ( <button onClick={() => { throw new Error('click boom'); }}> Click me </button> ); } // Correct approach for async errors function AsyncFixed() { const [error, setError] = useState(null); useEffect(() => { fetch('/api/data') .then(res => res.json()) .catch(err => setError(err.message)); // handle manually }, []); if (error) return <p>Error: {error}</p>; return <p>Loaded</p>; } ``` Wrapping these in `<ErrorBoundary>` does nothing for async or event handler throws. Those must be handled with `try/catch` or `.catch()` at the call site.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.