|$ curl https://forge-ai.dev/api/markdown?path=docs/react/error-boundaries
$cat docs/react-—-error-boundaries.md
updated Last week·13 min read·published

React — Error Boundaries

ReactIntermediate🎯Free Tools
Introduction

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the entire application. They work like a try-catch block for React rendering, lifecycle methods, and constructors.

Error boundaries do not catch errors in event handlers, async code (setTimeout, requestAnimationFrame), server-side rendering, or errors thrown in the boundary itself. For those cases, use regular try-catch blocks.

Class Error Boundary
error_boundary.tsx
TSX
1import React, { Component, ErrorInfo, ReactNode } from "react";
2
3interface ErrorBoundaryProps {
4 children: ReactNode;
5 fallback?: ReactNode;
6 onError?: (error: Error, errorInfo: ErrorInfo) => void;
7}
8
9interface ErrorBoundaryState {
10 hasError: boolean;
11 error: Error | null;
12}
13
14class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
15 constructor(props: ErrorBoundaryProps) {
16 super(props);
17 this.state = { hasError: false, error: null };
18 }
19
20 // Static method — called during render phase
21 // Returns updated state object, or null to update nothing
22 static getDerivedStateFromError(error: Error): ErrorBoundaryState {
23 return { hasError: true, error };
24 }
25
26 // Called after the error has been committed to the DOM
27 // Good for error reporting / logging
28 componentDidCatch(error: Error, errorInfo: ErrorInfo) {
29 console.error("Error caught by boundary:", error);
30 console.error("Component stack:", errorInfo.componentStack);
31
32 // Send to error reporting service
33 this.props.onError?.(error, errorInfo);
34 }
35
36 render() {
37 if (this.state.hasError) {
38 // Custom fallback UI
39 if (this.props.fallback) {
40 return this.props.fallback;
41 }
42
43 // Default fallback
44 return (
45 <div className="p-4 border border-red-500/30 rounded-lg bg-red-500/10">
46 <h2 className="text-sm font-mono text-red-400 mb-2">
47 Something went wrong
48 </h2>
49 <p className="text-xs font-mono text-[#A0A0A0]">
50 {this.state.error?.message}
51 </p>
52 <button
53 onClick={() => this.setState({ hasError: false, error: null })}
54 className="mt-3 px-3 py-1 text-xs font-mono text-red-400 border border-red-500/30 rounded hover:bg-red-500/10"
55 >
56 Try again
57 </button>
58 </div>
59 );
60 }
61
62 return this.props.children;
63 }
64}
65
66// Usage
67function App() {
68 return (
69 <ErrorBoundary
70 fallback={<p className="text-red-400">Failed to load dashboard</p>}
71 onError={(error, info) => {
72 logErrorToService(error, info.componentStack);
73 }}
74 >
75 <Dashboard />
76 </ErrorBoundary>
77 );
78}
Granular Error Boundaries
granular.tsx
TSX
1// Place error boundaries at different levels for different behaviors
2
3function App() {
4 return (
5 <ErrorBoundary fallback={<FullPageError />}>
6 <header>Nav</header>
7
8 <main>
9 <ErrorBoundary fallback={<SidebarError />}>
10 <Sidebar />
11 </ErrorBoundary>
12
13 <ErrorBoundary fallback={<ContentError />}>
14 <Content />
15 </ErrorBoundary>
16 </main>
17
18 <footer>Footer</footer>
19 </ErrorBoundary>
20 );
21}
22
23// Each boundary catches errors from its children independently
24// Sidebar crashing doesn't affect Content and vice versa
25
26// Reusable error boundary with reset
27function Dashboard() {
28 const [key, setKey] = useState(0);
29
30 return (
31 <ErrorBoundary
32 key={key}
33 fallback={
34 <div>
35 <p>Widget crashed</p>
36 <button onClick={() => setKey((k) => k + 1)}>Retry</button>
37 </div>
38 }
39 >
40 <Widget />
41 </ErrorBoundary>
42 );
43}
44
45// Error boundary per route
46function AppRouter() {
47 return (
48 <Routes>
49 <Route
50 path="/dashboard"
51 element={
52 <ErrorBoundary fallback={<DashboardError />}>
53 <DashboardPage />
54 </ErrorBoundary>
55 }
56 />
57 <Route
58 path="/settings"
59 element={
60 <ErrorBoundary fallback={<SettingsError />}>
61 <SettingsPage />
62 </ErrorBoundary>
63 }
64 />
65 </Routes>
66 );
67}
68
69// Separate UI state from error state
70function FeatureSection() {
71 const [error, setError] = useState<Error | null>(null);
72
73 if (error) {
74 return (
75 <div className="p-4 border border-yellow-500/30 rounded">
76 <p className="text-yellow-400 text-xs">Feature temporarily unavailable</p>
77 <button
78 onClick={() => setError(null)}
79 className="text-xs text-yellow-500 underline mt-2"
80 >
81 Retry
82 </button>
83 </div>
84 );
85 }
86
87 return <RiskyFeature onError={setError} />;
88}

info

Place error boundaries at the granularity you want. A boundary at the top catches everything but shows a full-page fallback. Boundaries around individual widgets keep the rest of the UI functional when one part crashes.
Error Reporting
error_reporting.tsx
TSX
1// Error boundaries are the ideal place to integrate error reporting
2
3// Sentry integration example
4import * as Sentry from "@sentry/react";
5
6function GlobalErrorBoundary({ children }: { children: React.ReactNode }) {
7 return (
8 <ErrorBoundary
9 fallback={<FallbackScreen />}
10 onError={(error, errorInfo) => {
11 // Report to Sentry with component stack
12 Sentry.withScope((scope) => {
13 scope.setExtras({
14 componentStack: errorInfo.componentStack,
15 });
16 Sentry.captureException(error);
17 });
18 }}
19 >
20 {children}
21 </ErrorBoundary>
22 );
23}
24
25// Custom error logging
26interface ErrorReport {
27 message: string;
28 stack?: string;
29 componentStack?: string;
30 url: string;
31 timestamp: number;
32 userId?: string;
33}
34
35function logError(error: Error, errorInfo: ErrorInfo) {
36 const report: ErrorReport = {
37 message: error.message,
38 stack: error.stack,
39 componentStack: errorInfo.componentStack ?? undefined,
40 url: window.location.href,
41 timestamp: Date.now(),
42 };
43
44 // Send to your error tracking service
45 fetch("/api/errors", {
46 method: "POST",
47 headers: { "Content-Type": "application/json" },
48 body: JSON.stringify(report),
49 }).catch(console.error);
50}
51
52// Context for error reporting
53const ErrorReporterContext = createContext<
54 ((error: Error, info: ErrorInfo) => void) | null
55>(null);
56
57function useReportError() {
58 return useContext(ErrorReporterContext);
59}
60
61// Usage in a component
62function RiskyComponent() {
63 const reportError = useReportError();
64
65 useEffect(() => {
66 try {
67 riskyOperation();
68 } catch (error) {
69 reportError?.(error as Error, { componentStack: "" });
70 }
71 }, []);
72}
Hooks Alternative Pattern
hooks_pattern.tsx
TSX
1// React doesn't have a hooks-based error boundary yet,
2// but we can create a wrapper pattern
3
4import { Component, ErrorInfo, ReactNode } from "react";
5
6// Reusable component-based wrapper
7interface ErrorFallbackProps {
8 error: Error;
9 reset: () => void;
10}
11
12function DefaultErrorFallback({ error, reset }: ErrorFallbackProps) {
13 return (
14 <div className="p-6 border border-red-500/30 rounded-lg bg-red-500/5">
15 <h2 className="text-sm font-mono text-red-400 mb-2">Error</h2>
16 <pre className="text-xs font-mono text-[#A0A0A0] mb-3 overflow-auto">
17 {error.message}
18 </pre>
19 <button
20 onClick={reset}
21 className="px-3 py-1 text-xs font-mono text-red-400 border border-red-500/30 rounded"
22 >
23 Try Again
24 </button>
25 </div>
26 );
27}
28
29// Higher-order component pattern
30function withErrorBoundary<P extends object>(
31 WrappedComponent: React.ComponentType<P>,
32 FallbackComponent: React.FC<ErrorFallbackProps> = DefaultErrorFallback
33) {
34 return class extends Component<
35 P,
36 { error: Error | null }
37 > {
38 state = { error: null as Error | null };
39
40 static getDerivedStateFromError(error: Error) {
41 return { error };
42 }
43
44 componentDidCatch(error: Error, info: ErrorInfo) {
45 console.error("withErrorBoundary:", error, info.componentStack);
46 }
47
48 reset = () => this.setState({ error: null });
49
50 render() {
51 if (this.state.error) {
52 return <FallbackComponent error={this.state.error} reset={this.reset} />;
53 }
54 return <WrappedComponent {...this.props} />;
55 }
56 };
57}
58
59// Usage of HOC
60const SafeDashboard = withErrorBoundary(Dashboard);
61const SafeChart = withErrorBoundary(Chart, CustomChartFallback);
62
63// Inline error boundary with render props
64function Catch({
65 fallback,
66 children,
67}: {
68 fallback: (error: Error, reset: () => void) => ReactNode;
69 children: ReactNode;
70}) {
71 return (
72 <ErrorBoundaryWrapper fallback={fallback}>
73 {children}
74 </ErrorBoundaryWrapper>
75 );
76}
77
78// Usage
79<Catch fallback={(error, reset) => (
80 <div>
81 <p>{error.message}</p>
82 <button onClick={reset}>Retry</button>
83 </div>
84)}>
85 <AsyncComponent />
86</Catch>

best practice

Error boundaries are still class components in React. Use the HOC pattern (withErrorBoundary) to apply them declaratively in function components without writing class syntax.
What Error Boundaries Don't Catch
what_not_catch.tsx
TSX
1// Error boundaries DO NOT catch errors in:
2// 1. Event handlers
3// 2. Asynchronous code (setTimeout, requestAnimationFrame, Promises)
4// 3. Server-side rendering
5// 4. Errors thrown in the boundary itself
6
7// ❌ Event handler errors — use try-catch
8function Button() {
9 function handleClick() {
10 try {
11 riskyOperation();
12 } catch (error) {
13 console.error("Button error:", error);
14 }
15 }
16
17 return <button onClick={handleClick}>Click</button>;
18}
19
20// ❌ Promise errors — use error boundaries + Suspense or .catch()
21async function fetchData() {
22 try {
23 const res = await fetch("/api/data");
24 if (!res.ok) throw new Error("Failed to fetch");
25 return res.json();
26 } catch (error) {
27 // Handle the error, or throw to trigger Suspense error boundary
28 throw error;
29 }
30}
31
32// ❌ useEffect errors — wrap in try-catch
33function DataLoader() {
34 useEffect(() => {
35 try {
36 riskySetup();
37 } catch (error) {
38 console.error("Setup error:", error);
39 }
40 }, []);
41}
42
43// ✅ Render errors — caught by error boundaries
44function RenderError() {
45 throw new Error("This IS caught by error boundary");
46}
47
48// ✅ Lifecycle method errors — caught by error boundaries
49class LifecycleComponent extends Component {
50 componentDidMount() {
51 // This IS caught
52 riskyInit();
53 }
54}
55
56// For async errors, use the pattern:
57function AsyncErrorBoundary({ children }: { children: React.ReactNode }) {
58 const [error, setError] = useState<Error | null>(null);
59
60 if (error) {
61 return <ErrorFallback error={error} reset={() => setError(null)} />;
62 }
63
64 return (
65 <ErrorBoundary fallback={(e) => { setError(e); return null; }}>
66 {children}
67 </ErrorBoundary>
68 );
69}
$Blueprint — Engineering Documentation·Section ID: REACT-ERR·Revision: 1.0