- Understand why error boundaries must be class components
- Use
getDerivedStateFromErrorto render a fallback UI during a render error - Use
componentDidCatchto log error details - Place boundaries at meaningful isolation zones so one failing section does not crash the entire app
- Build a reusable
useErrorHandlerhook that bridges async errors into the nearest boundary - Understand what error boundaries cannot catch (event handlers, async callbacks, server-side errors)
Open start/ and run the app. Everything looks fine — until something throws during a render. Because there are no error boundaries, any render error anywhere in the tree produces a white screen and the user has no path to recovery.
Run npm run dev inside start/ and try triggering the test crash buttons added to the UI.
File: src/components/ErrorBoundary.tsx
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: (props: { error: Error; resetError: () => void }) => ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}Requirements:
- Implement
static getDerivedStateFromError(error)— returns{ hasError: true, error } - Implement
componentDidCatch(error, errorInfo)— callprops.onErrorandconsole.error - Implement
resetErrorinstance method — resets state to{ hasError: false, error: null } - If
props.fallbackis provided, call it with{ error, resetError }; otherwise render<DefaultErrorFallback>
File: src/components/ErrorFallback.tsx
Export two components:
DefaultErrorFallback— shows error message, "Try Again" button, "Report Issue" linkGlobalErrorFallback— shows a full-page error, "Reload Application" button that callswindow.location.reload()
File: src/hooks/useErrorHandler.ts
export function useErrorHandler(): (error: Error) => voidThe hook stores an error in state. If the error is non-null, it throws during render, which is caught by the nearest ErrorBoundary.
| Location | File | What it protects |
|---|---|---|
| Global | src/App.tsx |
Entire application — last resort |
| Project list | src/pages/ProjectsLayout.tsx |
The <aside> project list |
| Project detail | src/pages/ProjectDetailPanel.tsx |
The detail panel content |
| Task form | src/components/TaskList.tsx |
The <AddTaskForm> section |
Each section boundary uses the resetError callback so "Try Again" re-renders the section.
The global boundary uses window.location.reload().
Error boundaries rely on two lifecycle methods that have no hook equivalents:
getDerivedStateFromError— called synchronously during the render phase; React uses its return value to re-render with a fallbackcomponentDidCatch— called during the commit phase for side effects (logging)
The React team has stated a hook-based API is planned but has not shipped as of React 19.
- Errors in event handlers (use try/catch there instead)
- Errors in async code (setTimeout, fetch callbacks) — use
useErrorHandlerto forward them - Errors thrown in the error boundary itself
- Server-side rendering errors
Place boundaries at meaningful "blast radius" boundaries:
- Too few → whole app crashes on any error
- Too many → excessive nesting with no benefit
- Good targets: route segments, independent data-fetching sections, third-party widgets
- Accept a
fallbackComponentprop (React element) in addition to the render-propfallback - Add a
resetKeysprop array — when any value in the array changes, automatically reset the boundary (as react-error-boundary does) - Integrate an error reporting service in
componentDidCatch(e.g.,Sentry.captureException) - Explore the
react-error-boundarylibrary and compare its API with your implementation