error-boundary
Error Boundary
Concept of the skill
An error boundary is React's recovery primitive for render-time failure: a component — still class-only as of React 19, because its defining methods (static getDerivedStateFromError, componentDidCatch) have no hooks equivalent — that catches JavaScript errors thrown anywhere in its descendant tree during rendering, lifecycle methods, and constructors, and renders a fallback UI instead of letting the throw unmount the broken subtree all the way to the root. It is a tree-level mechanism scoped by where you place it: a boundary near the page root catches everything but replaces the whole page on any error, while a boundary around a single widget catches only that widget's failure and lets the rest keep working — so placement is a granularity decision driven by failure-blast-radius, choosing the smallest scope where the fallback is still meaningful. Its defining limitation is the call-stack rule: it catches only what is thrown inside React's call stack during render or lifecycle, never event-handler throws, async/setTimeout/Promise rejections, SSR errors, or errors in the boundary itself — those must be caught locally and surfaced into render via setState to reach a boundary. It pairs with Suspense (a distinct primitive that catches thrown Promises rather than thrown Errors) and, to stay honest, must report every caught error to external observability so a fallback is the user-facing recovery and the developer-facing alarm rather than silent UI degradation.
Coverage
The discipline of placing and configuring React error boundaries: precisely what the boundary catches (rendering errors, lifecycle method errors, constructor errors) and what it provably cannot catch (event handler errors, async/setTimeout/Promise errors, server-side rendering errors before React 18.5, errors thrown by the boundary itself), why React 19 still requires class-component implementation, the boundary-placement granularity tradeoff (page / feature / leaf), the canonical Suspense+ErrorBoundary nesting and why their order matters, the reset-and-recover pattern via resetKeys / FallbackComponent, the Next.js App Router conventions (error.tsx, global-error.tsx), and how to wire boundaries to external error reporting so caught errors do not become silent UI degradation.
Philosophy of the skill
Before React 16, an error thrown during render had nowhere to go. React's reconciler would catch it, log it, and continue trying to render — usually producing a corrupted or blank tree. The component model assumed that render functions don't throw, and the runtime had no recovery primitive when they did. The result was either total app crash or partial blank screens with no signal that anything had broken.
Error boundaries gave React the recovery primitive it had been missing. A class component that implements getDerivedStateFromError and/or componentDidCatch catches throws from anywhere in its descendant subtree during rendering, lifecycle methods, and constructors. When a throw occurs, React unmounts the broken subtree and renders the boundary's fallback in its place. The rest of the tree, outside the boundary, keeps running. A bug in one widget no longer means a blank page; it means that widget shows its fallback and everything else is fine.
The discipline is to place boundaries by failure-blast-radius. A boundary near the page root catches everything but replaces the entire page on any error. A boundary around a single widget catches only that widget's errors but lets the rest of the page keep working. The right granularity is the smallest scope where the fallback UI is still meaningful: a chart's error fallback ("Unable to load chart, retry?") is useful per-chart; a global "Something went wrong" replacing the whole app is useful only as a last resort.
The boundary's most important second-order property is honesty. A caught error is still an error. A boundary that swallows the throw without reporting externally degrades the UI silently — users see "Something went wrong," developers see nothing. The discipline is to pair every boundary with a report-to-Sentry (or equivalent) call, so the fallback is the user-facing recovery and the developer-facing alarm.