Lesson 26 of 31
Error Boundaries
Catching a render error in a subtree before it takes down the whole app — why this specific job still requires a class component, and what error boundaries deliberately don't catch.
Every component you've written assumes rendering succeeds. Real components
sometimes throw — a malformed API response, an unexpected undefined, a
third-party library misbehaving. Error boundaries are React's mechanism
for containing that damage to a section of the UI instead of losing the
whole page.
What happens without one
By default, an error thrown during rendering anywhere in the tree is serious enough that React unmounts the affected tree rather than risk leaving a half-rendered, inconsistent UI on screen. With no boundary anywhere above the failure, that can mean the entire app goes blank.
function UserBio({ user }) {
return <p>{user.bio.toUpperCase()}</p>; // throws if user.bio is undefined
}One malformed user record, with no boundary in place, can take down everything — the header, the sidebar, unrelated panels that had nothing to do with the bad data.
Defining an error boundary
This is one of the few corners of modern React that still genuinely requires a class component — there is no hook-based equivalent as of React 18 or 19:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}getDerivedStateFromError runs during rendering, to decide what the
fallback UI should be; componentDidCatch runs afterward, as the place to
log or report the error. Wrapping part of the tree in it:
<ErrorBoundary fallback={<p>Something went wrong loading this section.</p>}>
<UserBio user={user} />
</ErrorBoundary>If UserBio throws, only this section shows the fallback message — the
rest of the page, outside the boundary, keeps working normally. In
practice, most real projects don't hand-write this class themselves; they
use a small, well-tested library like react-error-boundary, which wraps
the same mechanism in a reusable, more ergonomic component while still
relying on this exact class-based API underneath.
What it deliberately does not catch
Error boundaries only catch errors from the specific process of building the UI: render, lifecycle methods, and constructors of components below them. They do not catch errors from:
- Event handlers — a
throwinsideonClickneeds an ordinarytry/catch, since it runs later, outside of rendering. - Asynchronous code — an error inside a
setTimeoutcallback or an unhandled promise rejection inside an effect, for the same reason. - Errors in the boundary component itself — a boundary can't catch its own failures; that's why boundaries are usually kept intentionally simple.
function DeleteButton() {
function handleClick() {
try {
deleteItem();
} catch (error) {
// handled here, not by any error boundary above this
showToast("Failed to delete");
}
}
return <button onClick={handleClick}>Delete</button>;
}This split isn't a gap in the design — event handlers and effects run in response to something that already happened, outside the render process a boundary is watching, so ordinary JavaScript error handling is the right tool there.
Placement is a deliberate choice
A single boundary around the entire app turns any rendering error anywhere into "the whole app shows one fallback message" — better than a blank page, but coarse. Scoping boundaries around independent sections (a chat widget, a comments panel, a third-party embed) means one broken feature degrades gracefully while everything else keeps functioning — usually the more useful granularity for a real application.
Try it yourself
What to remember
- Without an error boundary, a render error can unmount the whole tree rather than leave a broken UI on screen — boundaries contain that damage to a subtree.
- Error boundaries still require a class component (getDerivedStateFromError / componentDidCatch) — one of the few places without a hook equivalent.
- They only catch errors from rendering, lifecycle methods, and constructors — not event handlers or async code, which still need ordinary try/catch.
- Scope boundaries around independent sections of the UI, not just the whole app, so one broken feature doesn't take everything else down with it.
Check yourself
4 questions · pass 3/4 to unlock Portals
1.What happens by default if a component throws an error during rendering, with no error boundary anywhere above it?
2.As of React 18/19, how do you define an error boundary?
3.A button's onClick handler throws an error. Does the error boundary wrapping that button catch it?
4.What should an error boundary's fallback UI typically do?
4 left to answer