Error Boundaries in React: Catching Crashes Gracefully
Error Boundaries: Catching Crashes Gracefully
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
An UNHANDLED JavaScript error while rendering a component (e.g. undefined.someProperty) crashes the ENTIRE React app by default – a white screen instead of a working application. Error boundaries prevent this by containing errors to a specific subtree.
Why plain try/catch doesn't work here
try/catch catches errors in IMPERATIVE code (function calls, loops) – but React's rendering process doesn't run inside your own try block, it runs DEEP inside React's own internal call chain. A try/catch AROUND <ProductCard /> in your own code simply does NOT catch an error that occurs WHILE rendering ProductCard.
The one remaining mandatory use of class components
A notable exception in otherwise hooks-based modern React: error boundaries MUST (as of React 18/19) be written as class components – there's no hook replacement for static getDerivedStateFromError and componentDidCatch. This is one of the few places you'll still see genuine class syntax in modern React.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
// Called during the render phase, right AFTER an error was thrown -
// must return the new state, MUST NOT cause side effects (see chapter 35
// on the render phase: it must stay "pure", even on error).
static getDerivedStateFromError(error) {
return { hasError: true };
}
// Called AFTER commit - side effects ARE allowed HERE,
// e.g. sending the error to an error-tracking service.
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary-fallback">
<h2>Something went wrong.</h2>
<p>This part of the page could not be loaded.</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;The two methods in detail
getDerivedStateFromErroris STATIC (no access tothis) and runs BEFORE the next render – its sole purpose is setting state so the fallback UI shows on the next render.componentDidCatchruns AFTER commit and additionally receiveserrorInfo(including the component stack) – actual logging/reporting belongs here, never ingetDerivedStateFromError.
In practice: protecting ProductListPage against bad API responses
We'll wrap the product list with ErrorBoundary in App.jsx – should ProductListPage (or one of its children) unexpectedly crash, the rest of the app (header, navigation) stays functional:
// In App.jsx, wrap ProductListPage:
import ErrorBoundary from './components/ErrorBoundary';
// ... in the Routes:
<Route
path="/"
element={
<ErrorBoundary>
<ProductListPage />
</ErrorBoundary>
}
/>Achtung: Error boundaries do NOT catch EVERYTHING: event handler errors (onClick={{() => {{ throw new Error() }} }}), asynchronous errors (setTimeout, fetch().then()), errors during server-side rendering, and errors INSIDE the error boundary component ITSELF are NOT caught. For event handler errors, regular try/catch inside the handler itself remains the right approach (see our own try/catch in ProductListPage's loadProducts() from "React for Beginners" chapter 23) – error boundaries are SPECIFICALLY for errors during rendering.
Strategic placement: not just ONE global boundary
A single ErrorBoundary at the very top around the ENTIRE app would replace the whole application with the fallback UI on ANY error – often overkill. Better: several, DELIBERATELY placed boundaries around independent page areas (like here around ProductListPage; you could similarly place one separately around CartWidget in the header) – an error in one area then doesn't take down the rest of the page with it.
Tipp: In production, componentDidCatch would typically call a service like Sentry or Bugsnag instead of just console.error – the error boundary is the ideal, central place for this, because it's GUARANTEED to see every otherwise-unhandled render error in its wrapped subtree, without every individual component needing to build in its own error handling.