A JavaScript error thrown during rendering unmounts the entire React tree by default — a single broken component can blank the whole page. An error boundary catches errors in its child tree and renders a fallback instead:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info);
}
render() {
if (this.state.hasError) return <h2>Something went wrong.</h2>;
return this.props.children;
}
}
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
Error boundaries must currently be class components — there's no hook equivalent, since the two lifecycle methods they rely on (getDerivedStateFromError, componentDidCatch) have no hook form. In practice, most apps write this boundary once and reuse it everywhere, rather than writing custom ones per feature.
Suspense handles a different problem: showing a fallback while something a component depends on isn't ready yet — most commonly, a lazily-loaded component:
const SettingsPage = React.lazy(() => import("./SettingsPage"));
<Suspense fallback={<Spinner />}>
<SettingsPage />
</Suspense>
React.lazy + Suspense together implement code splitting — SettingsPage's code isn't downloaded until it's actually needed, showing <Spinner /> in the meantime, which keeps the initial bundle smaller for users who never visit that page.