React Error Handling & Debugging

Master error handling techniques to build resilient React applications. Learn to implement error boundaries, handle async errors, debug effectively, and create great error recovery experiences.

Advertisement Space - top-error-handling

Google AdSense: horizontal

Interactive Error Boundary Demo

Error Boundaries

Catch and handle errors in component trees gracefully

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI.

Implementation

1// Error Boundary Implementation
2import React, { Component, ErrorInfo, ReactNode } from 'react';
3
4interface Props {
5 children: ReactNode;
6 fallback?: ReactNode;
7 onError?: (error: Error, errorInfo: ErrorInfo) => void;
8}
9
10interface State {
11 hasError: boolean;
12 error: Error | null;
13 errorInfo: ErrorInfo | null;
14}
15
16class ErrorBoundary extends Component<Props, State> {
17 constructor(props: Props) {
18 super(props);
19 this.state = {
20 hasError: false,
21 error: null,
22 errorInfo: null
23 };
24 }
25
26 static getDerivedStateFromError(error: Error): State {
27 // Update state so the next render will show the fallback UI
28 return {
29 hasError: true,
30 error,
31 errorInfo: null
32 };
33 }
34
35 componentDidCatch(error: Error, errorInfo: ErrorInfo) {
36 // Log error to error reporting service
37 console.error('Error caught by boundary:', error, errorInfo);
38
39 // Update state with error details
40 this.setState({
41 error,
42 errorInfo
43 });
44
45 // Call custom error handler if provided
46 this.props.onError?.(error, errorInfo);
47
48 // Send to error tracking service
49 if (typeof window !== 'undefined' && window.errorTracker) {
50 window.errorTracker.logError(error, {
51 componentStack: errorInfo.componentStack,
52 props: this.props
53 });
54 }
55 }
56
57 render() {
58 if (this.state.hasError) {
59 // Custom fallback UI
60 if (this.props.fallback) {
61 return <>{this.props.fallback}</>;
62 }
63
64 // Default error UI
65 return (
66 <div className="error-boundary-default">
67 <h2>Oops! Something went wrong</h2>
68 <details style={{ whiteSpace: 'pre-wrap' }}>
69 <summary>Click for details</summary>
70 {this.state.error && this.state.error.toString()}
71 <br />
72 {this.state.errorInfo && this.state.errorInfo.componentStack}
73 </details>
74 <button onClick={() => window.location.reload()}>
75 Reload Page
76 </button>
77 </div>
78 );
79 }
80
81 return this.props.children;
82 }
83}
84
85// Hook for error boundary functionality
86export function useErrorBoundary() {
87 const [error, setError] = useState<Error | null>(null);
88
89 const resetError = () => setError(null);
90
91 const captureError = (error: Error) => {
92 setError(error);
93 };
94
95 // Throw error to be caught by nearest error boundary
96 if (error) {
97 throw error;
98 }
99
100 return { captureError, resetError };
101}
102
103// Usage example
104function App() {
105 return (
106 <ErrorBoundary
107 fallback={<CustomErrorFallback />}
108 onError={(error, errorInfo) => {
109 // Send to monitoring service
110 logToService(error, errorInfo);
111 }}
112 >
113 <Header />
114 <ErrorBoundary fallback={<ErrorMessage />}>
115 <MainContent />
116 </ErrorBoundary>
117 <Footer />
118 </ErrorBoundary>
119 );
120}

Advanced Example

1// Advanced Error Boundary with recovery
2import { Component, ReactNode } from 'react';
3
4interface ErrorBoundaryState {
5 hasError: boolean;
6 error: Error | null;
7 errorCount: number;
8 lastErrorTime: number;
9}
10
11interface ErrorBoundaryProps {
12 children: ReactNode;
13 fallbackComponent?: React.ComponentType<{
14 error: Error;
15 retry: () => void;
16 errorCount: number;
17 }>;
18 onError?: (error: Error, errorInfo: ErrorInfo, errorCount: number) => void;
19 maxRetries?: number;
20 resetTimeout?: number;
21 isolate?: boolean;
22}
23
24export class AdvancedErrorBoundary extends Component<
25 ErrorBoundaryProps,
26 ErrorBoundaryState
27> {
28 private resetTimeoutId: number | null = null;
29
30 constructor(props: ErrorBoundaryProps) {
31 super(props);
32 this.state = {
33 hasError: false,
34 error: null,
35 errorCount: 0,
36 lastErrorTime: 0
37 };
38 }
39
40 static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
41 const now = Date.now();
42 return {
43 hasError: true,
44 error,
45 lastErrorTime: now
46 };
47 }
48
49 componentDidCatch(error: Error, errorInfo: ErrorInfo) {
50 const { onError, maxRetries = 3, resetTimeout = 10000 } = this.props;
51 const { errorCount, lastErrorTime } = this.state;
52 const now = Date.now();
53
54 // Reset error count if enough time has passed
55 const timeSinceLastError = now - lastErrorTime;
56 const newErrorCount = timeSinceLastError > resetTimeout ? 1 : errorCount + 1;
57
58 this.setState({ errorCount: newErrorCount });
59
60 // Log error
61 console.error(`Error Boundary caught error (${newErrorCount}/${maxRetries}):`, error);
62 onError?.(error, errorInfo, newErrorCount);
63
64 // Auto-reset after timeout
65 if (this.resetTimeoutId) {
66 clearTimeout(this.resetTimeoutId);
67 }
68
69 if (newErrorCount < maxRetries) {
70 this.resetTimeoutId = window.setTimeout(() => {
71 this.reset();
72 }, resetTimeout);
73 }
74 }
75
76 reset = () => {
77 if (this.resetTimeoutId) {
78 clearTimeout(this.resetTimeoutId);
79 this.resetTimeoutId = null;
80 }
81
82 this.setState({
83 hasError: false,
84 error: null,
85 errorCount: 0,
86 lastErrorTime: 0
87 });
88 };
89
90 render() {
91 const { hasError, error, errorCount } = this.state;
92 const { children, fallbackComponent: FallbackComponent, maxRetries = 3, isolate } = this.props;
93
94 if (hasError && error) {
95 // Too many errors - show permanent error state
96 if (errorCount >= maxRetries) {
97 return (
98 <div className="error-boundary-fatal">
99 <h2>⚠️ Multiple errors detected</h2>
100 <p>This component is experiencing repeated errors.</p>
101 <button onClick={() => window.location.reload()}>
102 Reload Application
103 </button>
104 </div>
105 );
106 }
107
108 // Custom fallback component
109 if (FallbackComponent) {
110 return (
111 <FallbackComponent
112 error={error}
113 retry={this.reset}
114 errorCount={errorCount}
115 />
116 );
117 }
118
119 // Default fallback
120 return (
121 <div className="error-boundary-fallback">
122 <h3>Something went wrong</h3>
123 <p>{error.message}</p>
124 <button onClick={this.reset}>Try Again</button>
125 <p className="error-count">
126 Error {errorCount} of {maxRetries}
127 </p>
128 </div>
129 );
130 }
131
132 // Isolate errors to this boundary
133 if (isolate) {
134 return <div className="error-isolation">{children}</div>;
135 }
136
137 return children;
138 }
139}
140
141// Async error boundary for Suspense
142export function AsyncErrorBoundary({
143 children,
144 fallback
145}: {
146 children: ReactNode;
147 fallback: ReactNode;
148}) {
149 return (
150 <ErrorBoundary fallback={fallback}>
151 <Suspense fallback={<Loading />}>
152 {children}
153 </Suspense>
154 </ErrorBoundary>
155 );
156}

Error Handling in Async Code

Handle errors in promises, async/await, and data fetching

Proper error handling in asynchronous operations is crucial for building robust React applications. Learn patterns for handling errors in various async scenarios.

Implementation

1// Async Error Handling Patterns
2import { useState, useEffect, useCallback } from 'react';
3
4// 1. Basic async error handling in components
5function UserProfile({ userId }: { userId: string }) {
6 const [user, setUser] = useState<User | null>(null);
7 const [loading, setLoading] = useState(true);
8 const [error, setError] = useState<Error | null>(null);
9
10 useEffect(() => {
11 let cancelled = false;
12
13 async function fetchUser() {
14 try {
15 setLoading(true);
16 setError(null);
17
18 const response = await fetch(`/api/users/${userId}`);
19
20 if (!response.ok) {
21 throw new Error(`Failed to fetch user: ${response.status} ${response.statusText}`);
22 }
23
24 const data = await response.json();
25
26 if (!cancelled) {
27 setUser(data);
28 }
29 } catch (err) {
30 if (!cancelled) {
31 setError(err instanceof Error ? err : new Error('Unknown error'));
32 console.error('Error fetching user:', err);
33 }
34 } finally {
35 if (!cancelled) {
36 setLoading(false);
37 }
38 }
39 }
40
41 fetchUser();
42
43 return () => {
44 cancelled = true;
45 };
46 }, [userId]);
47
48 if (loading) return <div>Loading...</div>;
49 if (error) return <ErrorDisplay error={error} retry={() => window.location.reload()} />;
50 if (!user) return <div>User not found</div>;
51
52 return <UserDetails user={user} />;
53}
54
55// 2. Custom hook for async operations with error handling
56function useAsyncOperation<T>() {
57 const [data, setData] = useState<T | null>(null);
58 const [error, setError] = useState<Error | null>(null);
59 const [loading, setLoading] = useState(false);
60
61 const execute = useCallback(async (asyncFunction: () => Promise<T>) => {
62 try {
63 setLoading(true);
64 setError(null);
65 const result = await asyncFunction();
66 setData(result);
67 return result;
68 } catch (err) {
69 const error = err instanceof Error ? err : new Error('Unknown error');
70 setError(error);
71 throw error; // Re-throw to allow caller to handle
72 } finally {
73 setLoading(false);
74 }
75 }, []);
76
77 const reset = useCallback(() => {
78 setData(null);
79 setError(null);
80 setLoading(false);
81 }, []);
82
83 return { data, error, loading, execute, reset };
84}
85
86// 3. Error handling with retries
87async function fetchWithRetry<T>(
88 fn: () => Promise<T>,
89 options: {
90 retries?: number;
91 delay?: number;
92 onRetry?: (error: Error, attempt: number) => void;
93 } = {}
94): Promise<T> {
95 const { retries = 3, delay = 1000, onRetry } = options;
96
97 let lastError: Error;
98
99 for (let attempt = 0; attempt <= retries; attempt++) {
100 try {
101 return await fn();
102 } catch (err) {
103 lastError = err instanceof Error ? err : new Error('Unknown error');
104
105 if (attempt < retries) {
106 onRetry?.(lastError, attempt + 1);
107 await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt)));
108 }
109 }
110 }
111
112 throw lastError!;
113}
114
115// 4. Global error handler for unhandled promise rejections
116export function setupGlobalErrorHandlers() {
117 // Handle unhandled promise rejections
118 window.addEventListener('unhandledrejection', (event) => {
119 console.error('Unhandled promise rejection:', event.reason);
120
121 // Prevent default browser behavior
122 event.preventDefault();
123
124 // Show user-friendly error message
125 showErrorNotification({
126 title: 'An unexpected error occurred',
127 message: 'Please refresh the page and try again',
128 error: event.reason
129 });
130
131 // Report to error tracking service
132 reportError(event.reason, {
133 type: 'unhandledRejection',
134 promise: event.promise
135 });
136 });
137
138 // Handle global errors
139 window.addEventListener('error', (event) => {
140 console.error('Global error:', event.error);
141
142 // Report to error tracking service
143 reportError(event.error, {
144 type: 'globalError',
145 message: event.message,
146 filename: event.filename,
147 lineno: event.lineno,
148 colno: event.colno
149 });
150 });
151}

Advanced Example

1// Advanced async error handling patterns
2import { useRef, useCallback, useEffect } from 'react';
3
4// Async error boundary using Suspense
5const AsyncErrorBoundary: React.FC<{
6 children: React.ReactNode;
7 fallback: (error: Error, retry: () => void) => React.ReactNode;
8}> = ({ children, fallback }) => {
9 const [error, setError] = useState<Error | null>(null);
10 const resetErrorBoundary = () => setError(null);
11
12 if (error) {
13 return <>{fallback(error, resetErrorBoundary)}</>;
14 }
15
16 return (
17 <ErrorBoundary
18 onError={setError}
19 fallback={<></>}
20 >
21 <Suspense fallback={<Loading />}>
22 {children}
23 </Suspense>
24 </ErrorBoundary>
25 );
26};
27
28// Race condition handling
29function useAsyncSafe<T>() {
30 const isMountedRef = useRef(true);
31 const abortControllerRef = useRef<AbortController>();
32
33 useEffect(() => {
34 return () => {
35 isMountedRef.current = false;
36 abortControllerRef.current?.abort();
37 };
38 }, []);
39
40 const executeSafe = useCallback(async (
41 asyncFn: (signal: AbortSignal) => Promise<T>
42 ) => {
43 // Cancel previous request
44 abortControllerRef.current?.abort();
45 abortControllerRef.current = new AbortController();
46
47 try {
48 const result = await asyncFn(abortControllerRef.current.signal);
49
50 if (isMountedRef.current) {
51 return result;
52 }
53 throw new Error('Component unmounted');
54 } catch (error) {
55 if (error instanceof Error) {
56 if (error.name === 'AbortError') {
57 console.log('Request was cancelled');
58 } else if (isMountedRef.current) {
59 throw error;
60 }
61 }
62 throw error;
63 }
64 }, []);
65
66 return executeSafe;
67}
68
69// Centralized error handling with context
70interface ErrorContextValue {
71 errors: Array<{ id: string; error: Error; timestamp: Date }>;
72 addError: (error: Error) => void;
73 removeError: (id: string) => void;
74 clearErrors: () => void;
75}
76
77const ErrorContext = createContext<ErrorContextValue | undefined>(undefined);
78
79export const ErrorProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
80 const [errors, setErrors] = useState<ErrorContextValue['errors']>([]);
81
82 const addError = useCallback((error: Error) => {
83 const id = Math.random().toString(36).substring(7);
84 setErrors(prev => [...prev, { id, error, timestamp: new Date() }]);
85
86 // Auto-remove after 5 seconds
87 setTimeout(() => {
88 removeError(id);
89 }, 5000);
90 }, []);
91
92 const removeError = useCallback((id: string) => {
93 setErrors(prev => prev.filter(e => e.id !== id));
94 }, []);
95
96 const clearErrors = useCallback(() => {
97 setErrors([]);
98 }, []);
99
100 return (
101 <ErrorContext.Provider value={{ errors, addError, removeError, clearErrors }}>
102 {children}
103 <ErrorNotifications errors={errors} onDismiss={removeError} />
104 </ErrorContext.Provider>
105 );
106};
107
108// Hook to use error context
109export function useError() {
110 const context = useContext(ErrorContext);
111 if (!context) {
112 throw new Error('useError must be used within ErrorProvider');
113 }
114 return context;
115}
116
117// Async operation with error context integration
118function useAsyncWithErrorHandling<T>() {
119 const { addError } = useError();
120 const executeSafe = useAsyncSafe<T>();
121
122 const execute = useCallback(async (
123 asyncFn: (signal: AbortSignal) => Promise<T>,
124 options?: {
125 showError?: boolean;
126 errorMessage?: string;
127 onError?: (error: Error) => void;
128 }
129 ) => {
130 try {
131 return await executeSafe(asyncFn);
132 } catch (error) {
133 const err = error instanceof Error ? error : new Error('Unknown error');
134
135 if (options?.showError !== false) {
136 addError(new Error(options?.errorMessage || err.message));
137 }
138
139 options?.onError?.(err);
140 throw err;
141 }
142 }, [executeSafe, addError]);
143
144 return execute;
145}

Form Validation Errors

Handle and display form validation errors effectively

Form validation is a critical part of user experience. Learn how to implement comprehensive error handling for forms with real-time validation and user-friendly error messages.

Implementation

1// Form Error Handling System
2import { useState, useCallback, useMemo } from 'react';
3
4// Types for form errors
5interface FieldError {
6 message: string;
7 type: 'required' | 'pattern' | 'minLength' | 'maxLength' | 'custom';
8}
9
10interface FormErrors<T> {
11 [K in keyof T]?: FieldError;
12}
13
14interface TouchedFields<T> {
15 [K in keyof T]?: boolean;
16}
17
18// Form validation hook
19function useFormValidation<T extends Record<string, any>>(
20 initialValues: T,
21 validationRules: Partial<Record<keyof T, ValidationRule[]>>
22) {
23 const [values, setValues] = useState<T>(initialValues);
24 const [errors, setErrors] = useState<FormErrors<T>>({});
25 const [touched, setTouched] = useState<TouchedFields<T>>({});
26 const [isSubmitting, setIsSubmitting] = useState(false);
27
28 // Validate single field
29 const validateField = useCallback(
30 (name: keyof T, value: any): FieldError | undefined => {
31 const rules = validationRules[name];
32 if (!rules) return undefined;
33
34 for (const rule of rules) {
35 const error = rule.validate(value, values);
36 if (error) {
37 return {
38 message: error,
39 type: rule.type
40 };
41 }
42 }
43 return undefined;
44 },
45 [validationRules, values]
46 );
47
48 // Validate all fields
49 const validateForm = useCallback((): boolean => {
50 const newErrors: FormErrors<T> = {};
51 let isValid = true;
52
53 Object.keys(validationRules).forEach((fieldName) => {
54 const error = validateField(fieldName as keyof T, values[fieldName]);
55 if (error) {
56 newErrors[fieldName as keyof T] = error;
57 isValid = false;
58 }
59 });
60
61 setErrors(newErrors);
62 return isValid;
63 }, [validateField, values, validationRules]);
64
65 // Handle field change
66 const handleChange = useCallback(
67 (name: keyof T, value: any) => {
68 setValues(prev => ({ ...prev, [name]: value }));
69
70 // Clear error when user starts typing
71 if (errors[name]) {
72 setErrors(prev => ({ ...prev, [name]: undefined }));
73 }
74 },
75 [errors]
76 );
77
78 // Handle field blur
79 const handleBlur = useCallback(
80 (name: keyof T) => {
81 setTouched(prev => ({ ...prev, [name]: true }));
82
83 // Validate on blur
84 const error = validateField(name, values[name]);
85 if (error) {
86 setErrors(prev => ({ ...prev, [name]: error }));
87 }
88 },
89 [validateField, values]
90 );
91
92 // Get field props
93 const getFieldProps = useCallback(
94 (name: keyof T) => ({
95 value: values[name],
96 onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
97 handleChange(name, e.target.value),
98 onBlur: () => handleBlur(name),
99 error: touched[name] ? errors[name] : undefined
100 }),
101 [values, errors, touched, handleChange, handleBlur]
102 );
103
104 return {
105 values,
106 errors,
107 touched,
108 isSubmitting,
109 handleChange,
110 handleBlur,
111 validateForm,
112 getFieldProps,
113 setFieldError: (name: keyof T, error: FieldError) =>
114 setErrors(prev => ({ ...prev, [name]: error })),
115 setIsSubmitting,
116 reset: () => {
117 setValues(initialValues);
118 setErrors({});
119 setTouched({});
120 setIsSubmitting(false);
121 }
122 };
123}
124
125// Validation rules
126interface ValidationRule {
127 type: 'required' | 'pattern' | 'minLength' | 'maxLength' | 'custom';
128 validate: (value: any, formValues: any) => string | undefined;
129}
130
131const validators = {
132 required: (message = 'This field is required'): ValidationRule => ({
133 type: 'required',
134 validate: (value) => (!value ? message : undefined)
135 }),
136
137 email: (message = 'Invalid email address'): ValidationRule => ({
138 type: 'pattern',
139 validate: (value) =>
140 value && !/^[^s@]+@[^s@]+.[^s@]+$/.test(value) ? message : undefined
141 }),
142
143 minLength: (min: number, message?: string): ValidationRule => ({
144 type: 'minLength',
145 validate: (value) =>
146 value && value.length < min
147 ? message || `Must be at least ${min} characters`
148 : undefined
149 }),
150
151 maxLength: (max: number, message?: string): ValidationRule => ({
152 type: 'maxLength',
153 validate: (value) =>
154 value && value.length > max
155 ? message || `Must be no more than ${max} characters`
156 : undefined
157 }),
158
159 pattern: (regex: RegExp, message: string): ValidationRule => ({
160 type: 'pattern',
161 validate: (value) =>
162 value && !regex.test(value) ? message : undefined
163 }),
164
165 custom: (
166 validateFn: (value: any, formValues: any) => boolean,
167 message: string
168 ): ValidationRule => ({
169 type: 'custom',
170 validate: (value, formValues) =>
171 !validateFn(value, formValues) ? message : undefined
172 })
173};
174
175// Usage example
176function RegistrationForm() {
177 const form = useFormValidation(
178 {
179 username: '',
180 email: '',
181 password: '',
182 confirmPassword: ''
183 },
184 {
185 username: [
186 validators.required(),
187 validators.minLength(3),
188 validators.pattern(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores')
189 ],
190 email: [
191 validators.required(),
192 validators.email()
193 ],
194 password: [
195 validators.required(),
196 validators.minLength(8),
197 validators.pattern(
198 /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)/,
199 'Must contain uppercase, lowercase, and number'
200 )
201 ],
202 confirmPassword: [
203 validators.required(),
204 validators.custom(
205 (value, formValues) => value === formValues.password,
206 'Passwords do not match'
207 )
208 ]
209 }
210 );
211
212 const handleSubmit = async (e: React.FormEvent) => {
213 e.preventDefault();
214
215 if (!form.validateForm()) {
216 return;
217 }
218
219 form.setIsSubmitting(true);
220
221 try {
222 await registerUser(form.values);
223 // Success handling
224 } catch (error) {
225 // Handle server errors
226 if (error.field) {
227 form.setFieldError(error.field, {
228 message: error.message,
229 type: 'custom'
230 });
231 }
232 } finally {
233 form.setIsSubmitting(false);
234 }
235 };
236
237 return (
238 <form onSubmit={handleSubmit}>
239 <FormField
240 label="Username"
241 {...form.getFieldProps('username')}
242 placeholder="Enter username"
243 />
244
245 <FormField
246 label="Email"
247 type="email"
248 {...form.getFieldProps('email')}
249 placeholder="Enter email"
250 />
251
252 <FormField
253 label="Password"
254 type="password"
255 {...form.getFieldProps('password')}
256 placeholder="Enter password"
257 />
258
259 <FormField
260 label="Confirm Password"
261 type="password"
262 {...form.getFieldProps('confirmPassword')}
263 placeholder="Confirm password"
264 />
265
266 <button
267 type="submit"
268 disabled={form.isSubmitting}
269 >
270 {form.isSubmitting ? 'Registering...' : 'Register'}
271 </button>
272 </form>
273 );
274}

Advanced Example

1// Advanced Form Error Component
2interface FormFieldProps {
3 label: string;
4 value: string;
5 onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
6 onBlur: () => void;
7 error?: FieldError;
8 type?: string;
9 placeholder?: string;
10 helpText?: string;
11}
12
13const FormField: React.FC<FormFieldProps> = ({
14 label,
15 error,
16 helpText,
17 ...inputProps
18}) => {
19 const hasError = !!error;
20
21 return (
22 <div className={`form-field ${hasError ? 'has-error' : ''}`}>
23 <label className="form-label">
24 {label}
25 {inputProps.required && <span className="required">*</span>}
26 </label>
27
28 <div className="input-wrapper">
29 <input
30 className={`form-input ${hasError ? 'error' : ''}`}
31 aria-invalid={hasError}
32 aria-describedby={hasError ? `${inputProps.name}-error` : undefined}
33 {...inputProps}
34 />
35
36 {hasError && (
37 <div className="error-icon">
38 <svg viewBox="0 0 20 20" fill="currentColor">
39 <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" />
40 </svg>
41 </div>
42 )}
43 </div>
44
45 {helpText && !hasError && (
46 <p className="help-text">{helpText}</p>
47 )}
48
49 {hasError && (
50 <p
51 id={`${inputProps.name}-error`}
52 className="error-message"
53 role="alert"
54 >
55 {error.message}
56 </p>
57 )}
58 </div>
59 );
60};
61
62// Error Summary Component
63const ErrorSummary: React.FC<{
64 errors: FormErrors<any>;
65 onFieldClick?: (fieldName: string) => void;
66}> = ({ errors, onFieldClick }) => {
67 const errorList = Object.entries(errors).filter(([_, error]) => error);
68
69 if (errorList.length === 0) return null;
70
71 return (
72 <div className="error-summary" role="alert" aria-live="polite">
73 <h3>Please fix the following errors:</h3>
74 <ul>
75 {errorList.map(([fieldName, error]) => (
76 <li key={fieldName}>
77 <button
78 type="button"
79 onClick={() => onFieldClick?.(fieldName)}
80 className="error-link"
81 >
82 {error.message}
83 </button>
84 </li>
85 ))}
86 </ul>
87 </div>
88 );
89};
90
91// Live validation feedback
92const ValidationFeedback: React.FC<{
93 field: string;
94 value: any;
95 rules: ValidationRule[];
96}> = ({ field, value, rules }) => {
97 const [feedback, setFeedback] = useState<{
98 [key: string]: { passed: boolean; message: string };
99 }>({});
100
101 useEffect(() => {
102 const newFeedback: typeof feedback = {};
103
104 rules.forEach((rule, index) => {
105 const error = rule.validate(value, {});
106 newFeedback[`${field}-${index}`] = {
107 passed: !error,
108 message: error || `Requirement ${index + 1} met`
109 };
110 });
111
112 setFeedback(newFeedback);
113 }, [value, rules, field]);
114
115 return (
116 <div className="validation-feedback">
117 {Object.entries(feedback).map(([key, { passed, message }]) => (
118 <div
119 key={key}
120 className={`feedback-item ${passed ? 'passed' : 'pending'}`}
121 >
122 <span className="feedback-icon">
123 {passed ? '✓' : '○'}
124 </span>
125 <span className="feedback-message">{message}</span>
126 </div>
127 ))}
128 </div>
129 );
130};

Debugging Techniques

Tools and techniques for debugging React applications

Master debugging techniques to quickly identify and fix issues in your React applications. Learn to use browser DevTools, React DevTools, and custom debugging utilities.

Implementation

1// React Debugging Utilities
2import { useEffect, useRef, useState } from 'react';
3
4// 1. Debug component renders
5export function useWhyDidYouRender(componentName: string, props: Record<string, any>) {
6 const previousProps = useRef<typeof props>();
7
8 useEffect(() => {
9 if (previousProps.current) {
10 const allKeys = Object.keys({ ...previousProps.current, ...props });
11 const changedProps: Record<string, any> = {};
12
13 allKeys.forEach(key => {
14 if (previousProps.current![key] !== props[key]) {
15 changedProps[key] = {
16 from: previousProps.current![key],
17 to: props[key]
18 };
19 }
20 });
21
22 if (Object.keys(changedProps).length) {
23 console.group(`[WhyDidYouRender] ${componentName}`);
24 console.log('Changed props:', changedProps);
25 console.log('All props:', props);
26 console.trace('Render stack trace');
27 console.groupEnd();
28 }
29 }
30
31 previousProps.current = props;
32 });
33}
34
35// 2. Debug hook for tracking state changes
36export function useStateWithLogger<T>(
37 initialState: T,
38 componentName: string,
39 stateName: string
40): [T, React.Dispatch<React.SetStateAction<T>>] {
41 const [state, setState] = useState<T>(initialState);
42
43 const setStateWithLog = useCallback((newState: React.SetStateAction<T>) => {
44 console.group(`[State Update] ${componentName}.${stateName}`);
45 console.log('Previous state:', state);
46
47 setState(prevState => {
48 const nextState = typeof newState === 'function'
49 ? (newState as (prev: T) => T)(prevState)
50 : newState;
51
52 console.log('Next state:', nextState);
53 console.trace('Update stack trace');
54 console.groupEnd();
55
56 return nextState;
57 });
58 }, [state, componentName, stateName]);
59
60 return [state, setStateWithLog];
61}
62
63// 3. Performance profiler
64export function usePerformanceProfiler(componentName: string) {
65 const renderCount = useRef(0);
66 const renderStartTime = useRef<number>();
67 const renderTimes = useRef<number[]>([]);
68
69 useEffect(() => {
70 renderCount.current += 1;
71 const renderEndTime = performance.now();
72
73 if (renderStartTime.current) {
74 const renderTime = renderEndTime - renderStartTime.current;
75 renderTimes.current.push(renderTime);
76
77 // Keep only last 10 renders
78 if (renderTimes.current.length > 10) {
79 renderTimes.current.shift();
80 }
81
82 const avgRenderTime =
83 renderTimes.current.reduce((a, b) => a + b, 0) / renderTimes.current.length;
84
85 if (renderTime > 16.67) { // Longer than one frame
86 console.warn(
87 `[Performance] ${componentName} slow render: ${renderTime.toFixed(2)}ms`
88 );
89 }
90
91 // Log every 10th render
92 if (renderCount.current % 10 === 0) {
93 console.log(`[Performance] ${componentName} stats:`, {
94 renderCount: renderCount.current,
95 lastRenderTime: `${renderTime.toFixed(2)}ms`,
96 avgRenderTime: `${avgRenderTime.toFixed(2)}ms`,
97 renderTimes: renderTimes.current.map(t => `${t.toFixed(2)}ms`)
98 });
99 }
100 }
101
102 renderStartTime.current = performance.now();
103 });
104}
105
106// 4. Debug context for development
107interface DebugContextValue {
108 logAction: (action: string, data?: any) => void;
109 logError: (error: Error, context?: any) => void;
110 enableLogging: boolean;
111 setEnableLogging: (enabled: boolean) => void;
112}
113
114const DebugContext = createContext<DebugContextValue | undefined>(undefined);
115
116export const DebugProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
117 const [enableLogging, setEnableLogging] = useState(
118 process.env.NODE_ENV === 'development'
119 );
120 const actionLog = useRef<Array<{ action: string; data: any; timestamp: Date }>>([]);
121
122 const logAction = useCallback((action: string, data?: any) => {
123 if (!enableLogging) return;
124
125 const logEntry = {
126 action,
127 data,
128 timestamp: new Date()
129 };
130
131 actionLog.current.push(logEntry);
132
133 console.log(`[Action] ${action}`, data);
134
135 // Keep only last 100 actions
136 if (actionLog.current.length > 100) {
137 actionLog.current.shift();
138 }
139 }, [enableLogging]);
140
141 const logError = useCallback((error: Error, context?: any) => {
142 console.error('[Error]', error.message, {
143 error,
144 context,
145 stack: error.stack
146 });
147 }, []);
148
149 // Expose debug info to window in development
150 useEffect(() => {
151 if (process.env.NODE_ENV === 'development') {
152 (window as any).__DEBUG__ = {
153 actionLog: actionLog.current,
154 enableLogging: () => setEnableLogging(true),
155 disableLogging: () => setEnableLogging(false),
156 clearLog: () => { actionLog.current = []; },
157 getLastActions: (count = 10) =>
158 actionLog.current.slice(-count)
159 };
160 }
161 }, []);
162
163 return (
164 <DebugContext.Provider value={{
165 logAction,
166 logError,
167 enableLogging,
168 setEnableLogging
169 }}>
170 {children}
171 </DebugContext.Provider>
172 );
173};

Advanced Example

1// Advanced debugging tools
2// Component tree inspector
3export function ComponentInspector({ children }: { children: ReactNode }) {
4 const [inspecting, setInspecting] = useState(false);
5 const [selectedComponent, setSelectedComponent] = useState<any>(null);
6
7 useEffect(() => {
8 if (!inspecting) return;
9
10 const handleClick = (e: MouseEvent) => {
11 e.preventDefault();
12 e.stopPropagation();
13
14 const element = e.target as HTMLElement;
15 const reactFiber = findReactFiber(element);
16
17 if (reactFiber) {
18 console.log('Selected component:', {
19 type: reactFiber.type,
20 props: reactFiber.memoizedProps,
21 state: reactFiber.memoizedState,
22 fiber: reactFiber
23 });
24 setSelectedComponent(reactFiber);
25 }
26
27 setInspecting(false);
28 };
29
30 document.addEventListener('click', handleClick, true);
31 document.body.style.cursor = 'crosshair';
32
33 return () => {
34 document.removeEventListener('click', handleClick, true);
35 document.body.style.cursor = 'auto';
36 };
37 }, [inspecting]);
38
39 return (
40 <>
41 {children}
42 {process.env.NODE_ENV === 'development' && (
43 <div className="component-inspector">
44 <button onClick={() => setInspecting(!inspecting)}>
45 {inspecting ? 'Inspecting...' : 'Inspect Component'}
46 </button>
47
48 {selectedComponent && (
49 <div className="component-details">
50 <h4>Component Details</h4>
51 <pre>{JSON.stringify({
52 type: selectedComponent.type?.name || 'Unknown',
53 props: selectedComponent.memoizedProps
54 }, null, 2)}</pre>
55 </div>
56 )}
57 </div>
58 )}
59 </>
60 );
61}
62
63// Network request debugger
64class NetworkDebugger {
65 private requests: Map<string, any> = new Map();
66 private enabled: boolean = process.env.NODE_ENV === 'development';
67
68 constructor() {
69 if (this.enabled) {
70 this.interceptFetch();
71 this.interceptXHR();
72 }
73 }
74
75 private interceptFetch() {
76 const originalFetch = window.fetch;
77
78 window.fetch = async (...args) => {
79 const [url, options] = args;
80 const id = Math.random().toString(36).substring(7);
81 const startTime = performance.now();
82
83 this.requests.set(id, {
84 url,
85 method: options?.method || 'GET',
86 headers: options?.headers,
87 body: options?.body,
88 startTime,
89 status: 'pending'
90 });
91
92 console.log(`[Network] Request started: ${options?.method || 'GET'} ${url}`);
93
94 try {
95 const response = await originalFetch(...args);
96 const endTime = performance.now();
97
98 this.requests.set(id, {
99 ...this.requests.get(id),
100 status: response.status,
101 statusText: response.statusText,
102 duration: endTime - startTime,
103 response: response.clone()
104 });
105
106 console.log(
107 `[Network] Request completed: ${options?.method || 'GET'} ${url} - ` +
108 `${response.status} (${(endTime - startTime).toFixed(2)}ms)`
109 );
110
111 return response;
112 } catch (error) {
113 const endTime = performance.now();
114
115 this.requests.set(id, {
116 ...this.requests.get(id),
117 error,
118 duration: endTime - startTime,
119 status: 'error'
120 });
121
122 console.error(
123 `[Network] Request failed: ${options?.method || 'GET'} ${url}`,
124 error
125 );
126
127 throw error;
128 }
129 };
130 }
131
132 private interceptXHR() {
133 const XHR = XMLHttpRequest.prototype;
134 const originalOpen = XHR.open;
135 const originalSend = XHR.send;
136
137 XHR.open = function(method: string, url: string, ...args: any[]) {
138 this._debugInfo = { method, url, startTime: 0 };
139 return originalOpen.apply(this, [method, url, ...args]);
140 };
141
142 XHR.send = function(body?: any) {
143 if (this._debugInfo) {
144 this._debugInfo.startTime = performance.now();
145 this._debugInfo.body = body;
146
147 console.log(
148 `[Network/XHR] Request started: ${this._debugInfo.method} ${this._debugInfo.url}`
149 );
150 }
151
152 return originalSend.apply(this, [body]);
153 };
154 }
155
156 getRequests() {
157 return Array.from(this.requests.values());
158 }
159
160 clearRequests() {
161 this.requests.clear();
162 }
163}
164
165// Initialize network debugger
166export const networkDebugger = new NetworkDebugger();
167
168// React Query devtools wrapper
169export function QueryDebugger({ children }: { children: ReactNode }) {
170 return (
171 <>
172 {children}
173 {process.env.NODE_ENV === 'development' && (
174 <div className="query-debugger">
175 <button onClick={() => {
176 const cache = queryClient.getQueryCache();
177 console.log('Query Cache:', cache.getAll());
178 }}>
179 Log Query Cache
180 </button>
181
182 <button onClick={() => {
183 queryClient.invalidateQueries();
184 console.log('Invalidated all queries');
185 }}>
186 Invalidate All Queries
187 </button>
188 </div>
189 )}
190 </>
191 );
192}

Error Recovery Strategies

Implement graceful error recovery and fallback mechanisms

Learn how to implement robust error recovery strategies that maintain a good user experience even when things go wrong.

Implementation

1// Error Recovery Components and Patterns
2import { Component, ReactNode, useState, useEffect } from 'react';
3
4// 1. Retry mechanism component
5interface RetryableProps {
6 children: ReactNode;
7 maxRetries?: number;
8 retryDelay?: number;
9 onError?: (error: Error, retryCount: number) => void;
10 fallback?: (error: Error, retry: () => void, retryCount: number) => ReactNode;
11}
12
13class RetryBoundary extends Component<RetryableProps, {
14 hasError: boolean;
15 error: Error | null;
16 retryCount: number;
17}> {
18 constructor(props: RetryableProps) {
19 super(props);
20 this.state = {
21 hasError: false,
22 error: null,
23 retryCount: 0
24 };
25 }
26
27 static getDerivedStateFromError(error: Error) {
28 return { hasError: true, error };
29 }
30
31 componentDidCatch(error: Error, errorInfo: ErrorInfo) {
32 const { onError } = this.props;
33 onError?.(error, this.state.retryCount);
34 }
35
36 retry = () => {
37 const { retryCount } = this.state;
38 const { maxRetries = 3, retryDelay = 1000 } = this.props;
39
40 if (retryCount < maxRetries) {
41 setTimeout(() => {
42 this.setState({
43 hasError: false,
44 error: null,
45 retryCount: retryCount + 1
46 });
47 }, retryDelay * Math.pow(2, retryCount)); // Exponential backoff
48 }
49 };
50
51 render() {
52 const { hasError, error, retryCount } = this.state;
53 const { children, fallback, maxRetries = 3 } = this.props;
54
55 if (hasError && error) {
56 if (fallback) {
57 return <>{fallback(error, this.retry, retryCount)}</>;
58 }
59
60 return (
61 <div className="error-retry">
62 <h3>Something went wrong</h3>
63 <p>{error.message}</p>
64 {retryCount < maxRetries && (
65 <button onClick={this.retry}>
66 Retry ({retryCount}/{maxRetries})
67 </button>
68 )}
69 </div>
70 );
71 }
72
73 return children;
74 }
75}
76
77// 2. Fallback data provider
78interface FallbackDataProviderProps<T> {
79 primaryDataSource: () => Promise<T>;
80 fallbackDataSource?: () => Promise<T>;
81 cachedData?: T;
82 children: (data: T, isStale: boolean) => ReactNode;
83 onError?: (error: Error, source: 'primary' | 'fallback') => void;
84}
85
86function FallbackDataProvider<T>({
87 primaryDataSource,
88 fallbackDataSource,
89 cachedData,
90 children,
91 onError
92}: FallbackDataProviderProps<T>) {
93 const [data, setData] = useState<T | null>(cachedData || null);
94 const [loading, setLoading] = useState(!cachedData);
95 const [isStale, setIsStale] = useState(false);
96 const [error, setError] = useState<Error | null>(null);
97
98 useEffect(() => {
99 let cancelled = false;
100
101 async function fetchData() {
102 try {
103 setLoading(true);
104 const result = await primaryDataSource();
105
106 if (!cancelled) {
107 setData(result);
108 setIsStale(false);
109 setError(null);
110 }
111 } catch (primaryError) {
112 onError?.(primaryError as Error, 'primary');
113
114 if (fallbackDataSource) {
115 try {
116 const fallbackResult = await fallbackDataSource();
117
118 if (!cancelled) {
119 setData(fallbackResult);
120 setIsStale(true);
121 setError(null);
122 }
123 } catch (fallbackError) {
124 onError?.(fallbackError as Error, 'fallback');
125
126 if (!cancelled) {
127 setError(fallbackError as Error);
128 // Use cached data if available
129 if (cachedData) {
130 setData(cachedData);
131 setIsStale(true);
132 }
133 }
134 }
135 } else {
136 if (!cancelled) {
137 setError(primaryError as Error);
138 }
139 }
140 } finally {
141 if (!cancelled) {
142 setLoading(false);
143 }
144 }
145 }
146
147 fetchData();
148
149 return () => {
150 cancelled = true;
151 };
152 }, [primaryDataSource, fallbackDataSource, cachedData, onError]);
153
154 if (loading && !data) {
155 return <div>Loading...</div>;
156 }
157
158 if (error && !data) {
159 return <div>Error: {error.message}</div>;
160 }
161
162 if (!data) {
163 return <div>No data available</div>;
164 }
165
166 return <>{children(data, isStale)}</>;
167}
168
169// 3. Circuit breaker pattern
170class CircuitBreaker {
171 private failureCount = 0;
172 private lastFailureTime = 0;
173 private state: 'closed' | 'open' | 'half-open' = 'closed';
174
175 constructor(
176 private threshold: number = 5,
177 private timeout: number = 60000, // 1 minute
178 private resetTimeout: number = 30000 // 30 seconds
179 ) {}
180
181 async execute<T>(fn: () => Promise<T>): Promise<T> {
182 if (this.state === 'open') {
183 if (Date.now() - this.lastFailureTime > this.timeout) {
184 this.state = 'half-open';
185 } else {
186 throw new Error('Circuit breaker is open');
187 }
188 }
189
190 try {
191 const result = await fn();
192
193 if (this.state === 'half-open') {
194 this.reset();
195 }
196
197 return result;
198 } catch (error) {
199 this.recordFailure();
200 throw error;
201 }
202 }
203
204 private recordFailure() {
205 this.failureCount++;
206 this.lastFailureTime = Date.now();
207
208 if (this.failureCount >= this.threshold) {
209 this.state = 'open';
210 console.warn('Circuit breaker opened due to repeated failures');
211
212 // Auto-reset after timeout
213 setTimeout(() => {
214 this.state = 'half-open';
215 }, this.resetTimeout);
216 }
217 }
218
219 private reset() {
220 this.failureCount = 0;
221 this.lastFailureTime = 0;
222 this.state = 'closed';
223 }
224
225 getState() {
226 return {
227 state: this.state,
228 failureCount: this.failureCount,
229 isOpen: this.state === 'open'
230 };
231 }
232}
233
234// Usage with React
235const apiCircuitBreaker = new CircuitBreaker();
236
237export function useCircuitBreaker<T>(
238 apiCall: () => Promise<T>
239) {
240 const [data, setData] = useState<T | null>(null);
241 const [error, setError] = useState<Error | null>(null);
242 const [circuitState, setCircuitState] = useState(apiCircuitBreaker.getState());
243
244 const execute = async () => {
245 try {
246 const result = await apiCircuitBreaker.execute(apiCall);
247 setData(result);
248 setError(null);
249 } catch (err) {
250 setError(err as Error);
251 } finally {
252 setCircuitState(apiCircuitBreaker.getState());
253 }
254 };
255
256 return { data, error, circuitState, execute };
257}

Advanced Example

1// Advanced error recovery system
2// Resilient data fetching with multiple strategies
3interface ResilienceConfig {
4 enableRetry?: boolean;
5 enableCache?: boolean;
6 enableFallback?: boolean;
7 enableCircuitBreaker?: boolean;
8 enableStaleWhileRevalidate?: boolean;
9}
10
11class ResilientDataFetcher {
12 private cache = new Map<string, { data: any; timestamp: number }>();
13 private circuitBreakers = new Map<string, CircuitBreaker>();
14
15 constructor(private config: ResilienceConfig = {}) {}
16
17 async fetch<T>(
18 key: string,
19 fetcher: () => Promise<T>,
20 options: {
21 cacheTime?: number;
22 fallback?: () => Promise<T>;
23 retries?: number;
24 staleTime?: number;
25 } = {}
26 ): Promise<{ data: T; isStale: boolean }> {
27 const {
28 cacheTime = 5 * 60 * 1000, // 5 minutes
29 fallback,
30 retries = 3,
31 staleTime = 60 * 1000 // 1 minute
32 } = options;
33
34 // Check cache first
35 if (this.config.enableCache) {
36 const cached = this.cache.get(key);
37 if (cached) {
38 const age = Date.now() - cached.timestamp;
39
40 if (age < cacheTime) {
41 // Return fresh cached data
42 return { data: cached.data, isStale: false };
43 } else if (this.config.enableStaleWhileRevalidate && age < staleTime) {
44 // Return stale data and revalidate in background
45 this.revalidateInBackground(key, fetcher, cacheTime);
46 return { data: cached.data, isStale: true };
47 }
48 }
49 }
50
51 // Get or create circuit breaker
52 if (this.config.enableCircuitBreaker) {
53 if (!this.circuitBreakers.has(key)) {
54 this.circuitBreakers.set(key, new CircuitBreaker());
55 }
56 }
57
58 // Try primary fetch with retry
59 let lastError: Error | null = null;
60
61 for (let attempt = 0; attempt <= (this.config.enableRetry ? retries : 0); attempt++) {
62 try {
63 const circuitBreaker = this.circuitBreakers.get(key);
64
65 const data = circuitBreaker
66 ? await circuitBreaker.execute(fetcher)
67 : await fetcher();
68
69 // Update cache
70 if (this.config.enableCache) {
71 this.cache.set(key, { data, timestamp: Date.now() });
72 }
73
74 return { data, isStale: false };
75 } catch (error) {
76 lastError = error as Error;
77
78 // Wait before retry with exponential backoff
79 if (attempt < retries && this.config.enableRetry) {
80 await new Promise(resolve =>
81 setTimeout(resolve, Math.pow(2, attempt) * 1000)
82 );
83 }
84 }
85 }
86
87 // Try fallback
88 if (this.config.enableFallback && fallback) {
89 try {
90 const data = await fallback();
91 return { data, isStale: true };
92 } catch (fallbackError) {
93 console.error('Fallback also failed:', fallbackError);
94 }
95 }
96
97 // Return cached data even if stale
98 if (this.config.enableCache) {
99 const cached = this.cache.get(key);
100 if (cached) {
101 return { data: cached.data, isStale: true };
102 }
103 }
104
105 throw lastError || new Error('Failed to fetch data');
106 }
107
108 private async revalidateInBackground(
109 key: string,
110 fetcher: () => Promise<any>,
111 cacheTime: number
112 ) {
113 try {
114 const data = await fetcher();
115 this.cache.set(key, { data, timestamp: Date.now() });
116 } catch (error) {
117 console.error('Background revalidation failed:', error);
118 }
119 }
120
121 clearCache(key?: string) {
122 if (key) {
123 this.cache.delete(key);
124 } else {
125 this.cache.clear();
126 }
127 }
128}
129
130// React hook for resilient data fetching
131export function useResilientData<T>(
132 key: string,
133 fetcher: () => Promise<T>,
134 options?: {
135 fallback?: () => Promise<T>;
136 dependencies?: any[];
137 config?: ResilienceConfig;
138 }
139) {
140 const [state, setState] = useState<{
141 data: T | null;
142 error: Error | null;
143 loading: boolean;
144 isStale: boolean;
145 }>({
146 data: null,
147 error: null,
148 loading: true,
149 isStale: false
150 });
151
152 const fetcherRef = useRef(
153 new ResilientDataFetcher(options?.config || {
154 enableRetry: true,
155 enableCache: true,
156 enableFallback: true,
157 enableCircuitBreaker: true,
158 enableStaleWhileRevalidate: true
159 })
160 );
161
162 useEffect(() => {
163 let cancelled = false;
164
165 async function loadData() {
166 setState(prev => ({ ...prev, loading: true, error: null }));
167
168 try {
169 const { data, isStale } = await fetcherRef.current.fetch(
170 key,
171 fetcher,
172 { fallback: options?.fallback }
173 );
174
175 if (!cancelled) {
176 setState({ data, error: null, loading: false, isStale });
177 }
178 } catch (error) {
179 if (!cancelled) {
180 setState(prev => ({
181 ...prev,
182 error: error as Error,
183 loading: false
184 }));
185 }
186 }
187 }
188
189 loadData();
190
191 return () => {
192 cancelled = true;
193 };
194 }, [key, ...(options?.dependencies || [])]);
195
196 const retry = useCallback(() => {
197 fetcherRef.current.clearCache(key);
198 const loadData = async () => {
199 setState(prev => ({ ...prev, loading: true, error: null }));
200
201 try {
202 const { data, isStale } = await fetcherRef.current.fetch(
203 key,
204 fetcher,
205 { fallback: options?.fallback }
206 );
207
208 setState({ data, error: null, loading: false, isStale });
209 } catch (error) {
210 setState(prev => ({
211 ...prev,
212 error: error as Error,
213 loading: false
214 }));
215 }
216 };
217
218 loadData();
219 }, [key, fetcher, options?.fallback]);
220
221 return { ...state, retry };
222}

Advertisement Space - mid-error-handling

Google AdSense: rectangle

Error Handling Best Practices

Error Boundaries

  • Place error boundaries at strategic points in component tree
  • Provide meaningful fallback UI for different error types
  • Log errors to monitoring services for analysis
  • Implement recovery mechanisms like retry buttons
  • Avoid catching errors in event handlers (use try-catch instead)
  • Test error boundaries with error simulation

Async Error Handling

  • Always handle promise rejections and async errors
  • Use try-catch blocks in async functions
  • Implement proper cleanup in useEffect
  • Handle race conditions with cleanup functions
  • Provide loading and error states for async operations
  • Use AbortController for cancellable requests

User Experience

  • Show user-friendly error messages, not technical details
  • Provide clear actions users can take to recover
  • Maintain application state during errors when possible
  • Use progressive degradation for non-critical features
  • Implement offline support with service workers
  • Test error scenarios thoroughly

Common React Errors & Solutions

TypeError: Cannot read property 'map' of undefined

Trying to map over data that hasn't loaded yet.

// ❌ Bad {data.map(item => <Item key={item.id} {...item} />)} // ✅ Good {data && data.map(item => <Item key={item.id} {...item} />)} {data?.map(item => <Item key={item.id} {...item} />)}

Warning: Can't perform a React state update on an unmounted component

Updating state after component unmounts.

// ✅ Use cleanup function useEffect(() => { let cancelled = false; fetchData().then(data => { if (!cancelled) { setData(data); } }); return () => { cancelled = true; }; }, []);

Advertisement Space - bottom-error-handling

Google AdSense: horizontal

Related Topics

Build Resilient React Applications

Master error handling to create applications that gracefully handle failures.

Learn Testing Next →