# Error Boundaries in React --- ## 1. Executive Summary & Strategic Necessity ### 1.1 Context (ภาษาไทย) Error Boundaries เป็นคุณลักษณะที่สำคัญใน React ที่ช่วยจัดการข้อผิดพลาด (Error Handling) ในแอปพลิเคชัน React โดยการจับ JavaScript errors ที่เกิดขึ้นใน component tree และแสดง fallback UI แทนที่จะทำให้ทั้งแอปพลิเคชัน crash นี่เป็นสิ่งสำคัญในการสร้างแอปพลิเคชันที่มีความเสถียรและให้ประสบการณ์ผู้ใช้ที่ดี Error Boundaries ถูกนำเสนอใน React 16 และเป็นส่วนสำคัญของการสร้างแอปพลิเคชัน React ที่มีความทนทาน (Resilient) และสามารถกู้คืนจากข้อผิดพลาดได้อย่างสวยงาม ### 1.2 Business Impact (ภาษาไทย) **ผลกระทบทางธุรกิจ:** 1. **ลด Downtime** - Error Boundaries ช่วยลดเวลาที่แอปพลิเคชันไม่สามารถใช้งานได้เนื่องจากข้อผิดพลาด โดยการจับและจัดการข้อผิดพลาดในระดับ component แทนที่จะทำให้ทั้งแอป crash 2. **เพิ่ม User Retention** - ผู้ใช้มักจะออกจากแอปพลิเคชันที่มีข้อผิดพลาดบ่อย Error Boundaries ช่วยให้แอปพลิเคชันยังคงใช้งานได้แม้จะมีข้อผิดพลาดในบางส่วน 3. **ลด Support Cost** - Error Reporting ที่ดีช่วยให้ทีมพัฒนาสามารถระบุและแก้ไขปัญหาได้เร็วขึ้น ทำให้ลดคำถามและปัญหาจากผู้ใช้ 4. **เพิ่ม Trust** - แอปพลิเคชันที่มีการจัดการข้อผิดพลาดที่ดีสร้างความไว้วางใจให้กับผู้ใช้ 5. **ปรับปรุง User Experience** - Fallback UI ที่ดีช่วยให้ผู้ใช้เข้าใจสถานการณ์และมีตัวเลือกในการกู้คืน ### 1.3 Product Thinking (ภาษาไทย) **มุมมองด้านผลิตภัณฑ์:** 1. **Graceful Degradation** - Error Boundaries ช่วยให้แอปพลิเคชันสามารถทำงานได้แม้จะมีบางส่วนที่ไม่ทำงาน ผู้ใช้ยังสามารถใช้งานส่วนอื่นๆ ของแอปพลิเคชันได้ 2. **Error Recovery** - ให้ผู้ใช้มีตัวเลือกในการกู้คืนจากข้อผิดพลาด เช่น Retry, Go Home, Contact Support 3. **Contextual Error Messages** - Error messages ต้องเข้าใจง่ายและให้ข้อมูลที่เป็นประโยชน์แก่ผู้ใช้ 4. **Error Tracking** - Error reporting ต้องมี context เพียงพอสำหรับการ debug เช่น component stack, user info, environment 5. **User-Centric Design** - Fallback UI ต้องออกแบบให้ใช้งานง่ายและให้ผู้ใช้มีความรู้สึกดีแม้จะเกิดข้อผิดพลาด --- ## 2. Technical Deep Dive (The "How-to") ### 2.1 Core Logic Error Boundaries คือ React components ที่: 1. จับ JavaScript errors ใน component tree ของตนเอง 2. Log errors สำหรับการ debug 3. แสดง fallback UI แทน component tree ที่ crashed **สิ่งที่ Error Boundaries จับได้:** - Rendering errors - Errors ใน lifecycle methods - Errors ใน constructors **สิ่งที่ Error Boundaries ไม่จับได้:** - Event handlers - Asynchronous code (setTimeout, promises) - Server-side rendering errors - Errors ที่เกิดใน error boundary เอง ### 2.2 Architecture Diagram Requirements ``` ┌─────────────────────────────────────────────────────────────────┐ │ Error Boundary Architecture │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Global Error Boundary │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ App Component │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │ │ │ │ │ Header │ │ Feature 1 │ │ Footer │ │ │ │ │ │ │ └─────────────┘ └──────┬──────┘ └───────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────▼──────────────────────┐ │ │ │ │ │ │ │ Feature Error Boundary │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ │ Component A │ │ Component B │ │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ │ Component C │ │ Component D │ │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ └───────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ │ │ Error Reporting Service │ │ │ │ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ │ │ │ Sentry │ │LogRocket│ │ Custom │ │ │ │ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Fallback UI Layer │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Simple │ │ Detailed │ │ Themed │ │ │ │ │ │ Fallback │ │ Fallback │ │ Fallback │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.3 Implementation Workflow **Step 1: Create Base Error Boundary Component** ```typescript // components/ErrorBoundary.tsx import React, { Component, ErrorInfo, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode onError?: (error: Error, errorInfo: ErrorInfo) => void } interface State { hasError: boolean error: Error | null errorInfo: ErrorInfo | null } export class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false, error: null, errorInfo: null } } static getDerivedStateFromError(error: Error): State { // Update state so the next render will show the fallback UI return { hasError: true, error, errorInfo: null } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Log the error to an error reporting service console.error('Error caught by boundary:', error, errorInfo) // Call custom error handler this.props.onError?.(error, errorInfo) // Store error info for display this.setState({ errorInfo }) } resetError = () => { this.setState({ hasError: false, error: null, errorInfo: null }) } render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback } // Default fallback return (

Something went wrong

{this.state.error && (

{this.state.error.message}

)}
) } return this.props.children } } ``` **Step 2: Create Error Reporting Service** ```typescript // services/ErrorReportingService.ts import * as Sentry from '@sentry/react' export class ErrorReportingService { static async report(error: Error, errorInfo?: ErrorInfo, context?: Record) { // Send to Sentry Sentry.withScope((scope) => { if (errorInfo) { scope.setExtra('componentStack', errorInfo.componentStack) } // Add custom context if (context) { Object.entries(context).forEach(([key, value]) => { scope.setExtra(key, value) }) } scope.setTag('errorBoundary', 'true') Sentry.captureException(error) }) // Also log to console in development if (process.env.NODE_ENV === 'development') { console.error('Error reported:', error, errorInfo, context) } } static setUser(user: { id: string; email?: string; username?: string }) { Sentry.setUser(user) } static setTag(key: string, value: string) { Sentry.setTag(key, value) } static clearUser() { Sentry.setUser(null) } } ``` **Step 3: Create Fallback UI Components** ```typescript // components/ErrorFallback.tsx import React from 'react' interface SimpleFallbackProps { error?: Error | null onReset?: () => void } export function SimpleFallback({ error, onReset }: SimpleFallbackProps) { return (

Something went wrong

We're sorry for the inconvenience.

{error &&

{error.message}

} {onReset && }
) } interface DetailedFallbackProps { error?: Error | null errorInfo?: ErrorInfo | null onReset?: () => void eventId?: string } export function DetailedFallback({ error, errorInfo, onReset, eventId }: DetailedFallbackProps) { return (
⚠️

Oops! Something went wrong

Error Message

{error?.message || 'Unknown error'}

{errorInfo && (
Technical Details
{errorInfo.componentStack}
)}
{onReset && ( )}
{eventId && (

Error ID: {eventId}

)}

Need help? Contact Support

) } ``` **Step 4: Implement Placement Strategy** ```typescript // App.tsx import { ErrorBoundary } from './components/ErrorBoundary' import { DetailedFallback } from './components/ErrorFallback' import { ErrorReportingService } from './services/ErrorReportingService' function App() { return ( // Global boundary for catastrophic errors } onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'global', timestamp: new Date().toISOString(), }) }} >
{/* Feature-specific boundaries */} window.location.href = '/dashboard'} /> } onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'dashboard', feature: 'dashboard', }) }} > window.location.href = '/projects'} /> } onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'projects', feature: 'projects', }) }} >
) } ``` --- ## 3. Tooling & Tech Stack ### 3.1 Enterprise Tools | Tool | Purpose | Version | License | |------|---------|---------|---------| | React | UI Library | ^18.0.0 | MIT | | react-error-boundary | Error Boundary Library | ^4.0.0 | MIT | | Sentry | Error Tracking | ^7.0.0 | Commercial/Free | | LogRocket | Session Recording | ^3.0.0 | Commercial | | TypeScript | Type Safety | ^5.0.0 | Apache 2.0 | ### 3.2 Configuration Essentials **Sentry Setup:** ```typescript // sentry.ts import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, integrations: [ new BrowserTracing(), new Sentry.Replay({ maskAllText: true, blockAllMedia: true, }), ], tracesSampleRate: 0.1, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, beforeSend(event) { // Filter out sensitive data if (event.request?.cookies) { delete event.request.cookies } return event }, }) ``` **react-error-boundary Setup:** ```bash npm install react-error-boundary ``` ```typescript // ErrorBoundaryProvider.tsx import { ErrorBoundary } from 'react-error-boundary' export function AppErrorBoundary({ children }: { children: React.ReactNode }) { const logError = (error: Error, info: { componentStack: string }) => { console.error('Error caught by boundary:', error, info) // Send to error tracking service ErrorReportingService.report(error, info) } return ( { // Optional: reset app state window.location.href = '/' }} > {children} ) } ``` --- ## 4. Standards, Compliance & Security ### 4.1 International Standards - **WCAG 2.1 Level AA** - Error messages ต้องเข้าถึงได้และเข้าใจง่าย - **ISO 25010** - Quality Model สำหรับ Reliability และ Recoverability - **OWASP** - Error Handling Best Practices สำหรับ Security ### 4.2 Security Protocol Error Boundaries ต้องปฏิบัติตามหลักความปลอดภัย: 1. **No Sensitive Data in Errors** - ไม่แสดงข้อมูลที่ละเอียดอ่อนใน error messages 2. **Error Logging** - Log errors อย่างปลอดภัยโดยไม่เก็บข้อมูลส่วนบุคคล 3. **Sanitization** - Sanitize error messages ก่อนแสดงใน UI 4. **Rate Limiting** - จำกัดจำนวน errors ที่ส่งไปยัง error tracking service ```typescript // Secure Error Reporting export class SecureErrorReportingService { static sanitizeError(error: Error): Error { // Remove sensitive data from error message const sanitizedMessage = error.message .replace(/password=["'][^"']*["']/gi, 'password="***"') .replace(/token=["'][^"']*["']/gi, 'token="***"') .replace(/api[_-]?key=["'][^"']*["']/gi, 'api_key="***"') return new Error(sanitizedMessage) } static report(error: Error, errorInfo?: ErrorInfo) { const sanitizedError = this.sanitizeError(error) // Send to error tracking service ErrorReportingService.report(sanitizedError, errorInfo) } } ``` ### 4.3 Explainability Error Boundaries ต้องสามารถอธิบายได้ว่า: 1. **Error Context** - Error เกิดขึ้นที่ไหนและเมื่อไร 2. **User Impact** - Error นี้ส่งผลกระทบต่อผู้ใช้อย่างไร 3. **Recovery Path** - ผู้ใช้สามารถกู้คืนจาก error ได้อย่างไร 4. **Debug Information** - ข้อมูลที่จำเป็นสำหรับการ debug --- ## 5. Unit Economics & Performance Metrics (KPIs) ### 5.1 Cost Calculation | Metric | Calculation | Target | |--------|-------------|--------| | Error Rate | Errors / Total Requests | < 0.1% | | Crash Rate | Crashes / Total Sessions | < 0.01% | | Recovery Rate | Recovered Errors / Total Errors | > 95% | | MTTR (Mean Time To Recovery) | Average recovery time | < 5 min | | Error Reporting Latency | Time to report error | < 1s | ### 5.2 Key Performance Indicators **Technical Metrics:** 1. **Error Rate** - จำนวน errors ต่อจำนวน requests 2. **Crash Rate** - จำนวน crashes ต่อจำนวน sessions 3. **Recovery Rate** - อัตราการกู้คืนจาก errors 4. **Error Reporting Latency** - เวลาในการ report error **Business Metrics:** 1. **User Retention** - อัตราการกลับมาใช้งานหลังจากเกิด error 2. **Support Tickets** - จำนวน support tickets ที่เกี่ยวข้องกับ errors 3. **User Satisfaction** - ความพึงพอใจของผู้ใช้หลังจากเกิด error 4. **Time to Resolution** - เวลาในการแก้ไข errors --- ## 6. Strategic Recommendations (CTO Insights) ### 6.1 Phase Rollout **Phase 1: Foundation (Week 1-2)** - Create base Error Boundary component - Setup error reporting service (Sentry) - Create fallback UI components - Implement global error boundary **Phase 2: Feature-Level Boundaries (Week 3-4)** - Add feature-level error boundaries - Implement contextual fallback UIs - Add error reporting for each feature - Test error boundaries with intentional errors **Phase 3: Advanced Features (Week 5-6)** - Implement retry mechanisms - Add error rate monitoring - Create error recovery strategies - Implement error boundary composition **Phase 4: Optimization (Week 7-8)** - Performance audit - Error rate analysis - User feedback collection - Documentation and training ### 6.2 Pitfalls to Avoid 1. **Over-Engineering** - Error boundaries มากเกินไปทำให้โค้ดซับซ้อน 2. **Ignoring Non-Catchable Errors** - ไม่จัดการ async errors และ event handler errors 3. **Poor Fallback UI** - Fallback UI ที่ไม่เป็นประโยชน์สำหรับผู้ใช้ 4. **Missing Error Context** - Error reporting ไม่มี context เพียงพอ 5. **No Recovery Mechanism** - ไม่มีวิธีให้ผู้ใช้กู้คืนจาก errors ### 6.3 Best Practices Checklist - [ ] ใช้ global error boundary สำหรับ catastrophic errors - [ ] ใช้ feature-level error boundaries สำหรับ better UX - [ ] Integrate กับ error tracking service (Sentry, LogRocket) - [ ] Provide fallback UI ที่ชัดเจนและเป็นประโยชน์ - [ ] Implement retry mechanisms - [ ] Log errors ด้วย context เพียงพอ - [ ] Test error boundaries ด้วย intentional errors - [ ] Monitor error rates และ set up alerts - [ ] Sanitize error messages ก่อนแสดงใน UI - [ ] Document error boundary patterns --- ## 7. Implementation Examples ### 7.1 Basic Error Boundary ```jsx class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { // Update state so next render shows fallback UI return { hasError: true, error }; } render() { if (this.state.hasError) { return ; } return this.props.children; } } ``` ### 7.2 Error Boundary with componentDidCatch ```jsx class ErrorBoundary extends React.Component { state = { hasError: false, error: null, errorInfo: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { // Log error to an error reporting service logErrorToService(error, errorInfo); // Store error info for display this.setState({ errorInfo }); } render() { if (this.state.hasError) { return ; } return this.props.children; } } function logErrorToService(error, errorInfo) { // Send to Sentry, LogRocket, etc. Sentry.captureException(error, { contexts: { react: { componentStack: errorInfo.componentStack, }, }, }); } ``` ### 7.3 Complete Error Boundary Example ```jsx class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, errorInfo: null }; } static getDerivedStateFromError(error) { // Update state to render fallback UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // Log error details console.error('Error caught by boundary:', error, errorInfo); // Send to error tracking service this.logError(error, errorInfo); // Store error info this.setState({ error, errorInfo, }); } logError(error, errorInfo) { // Example: Send to Sentry if (typeof Sentry !== 'undefined') { Sentry.withScope((scope) => { scope.setExtra('componentStack', errorInfo.componentStack); Sentry.captureException(error); }); } } render() { if (this.state.hasError) { return this.props.fallback({ error: this.state.error, errorInfo: this.state.errorInfo, resetError: this.resetError.bind(this), }); } return this.props.children; } resetError() { this.setState({ hasError: false, error: null, errorInfo: null }); } } // Usage (

Something went wrong

{error?.message}

)} >
``` ### 7.4 Error Boundary Limitations ```jsx class MyComponent extends React.Component { handleClick() { // Event handlers are NOT caught try { throw new Error('Event handler error'); } catch (error) { // Must handle manually console.error(error); } } async componentDidMount() { // Async errors are NOT caught try { const data = await fetchData(); // Error here won't be caught } catch (error) { // Must handle manually console.error(error); } // setTimeout errors are NOT caught setTimeout(() => { throw new Error('Timeout error'); // Won't be caught }, 1000); } render() { return ; } } ``` ### 7.5 Handling Non-Catchable Errors ```jsx // For async errors, wrap in try-catch class MyComponent extends React.Component { state = { data: null, error: null }; async componentDidMount() { try { const data = await fetchData(); this.setState({ data }); } catch (error) { this.setState({ error }); // Log error logError(error); } } render() { if (this.state.error) { return ; } if (!this.state.data) { return ; } return ; } } ``` ### 7.6 Placement Strategies **Granular (Feature-Level):** ```jsx function App() { return (
}> }> }>
); } ``` **Global (App-Level):** ```jsx function App() { return ( }> } /> } /> } /> ); } ``` **Hybrid Approach:** ```jsx function App() { return ( // Global boundary for catastrophic errors }>
{/* Feature-specific boundaries */} }> }>
); } ``` ### 7.7 Fallback UI Patterns **Simple Fallback:** ```jsx function SimpleFallback({ error, resetError }) { return (

Something went wrong

We're sorry for inconvenience.

{error &&

{error.message}

}
); } ``` **Detailed Fallback:** ```jsx function DetailedFallback({ error, errorInfo, resetError }) { return (
⚠️

Oops! Something went wrong

Error Message

{error?.message || 'Unknown error'}

{errorInfo && (
Technical Details
{errorInfo.componentStack}
)}

Need help? Contact Support

); } ``` **Themed Fallback:** ```jsx function ThemedFallback({ error, resetError }) { const theme = useTheme(); return (
{theme === 'dark' ? ( ) : ( )}

Something went wrong

{error?.message}

); } ``` ### 7.8 Error Reporting Integration **Sentry Integration:** ```jsx import * as Sentry from '@sentry/react'; class SentryErrorBoundary extends React.Component { state = { eventId: null }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { // Send error to Sentry Sentry.withScope((scope) => { scope.setExtra('componentStack', errorInfo.componentStack); scope.setTag('errorBoundary', 'true'); const eventId = Sentry.captureException(error); this.setState({ eventId }); }); } render() { if (this.state.hasError) { return ( window.location.reload()} /> ); } return this.props.children; } } // Initialize Sentry Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, release: process.env.APP_VERSION, }); ``` **LogRocket Integration:** ```jsx import LogRocket from 'logrocket'; class LogRocketErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { // Log error to LogRocket LogRocket.captureException(error, { tags: { errorBoundary: 'true', }, extra: { componentStack: errorInfo.componentStack, }, }); } render() { if (this.state.hasError) { return ; } return this.props.children; } } // Initialize LogRocket LogRocket.init('YOUR_APP_ID', { release: process.env.APP_VERSION, console: { isEnabled: process.env.NODE_ENV === 'development', }, }); ``` **Custom Error Service:** ```jsx class ErrorReportingService { static async report(error, errorInfo) { const payload = { message: error.message, stack: error.stack, componentStack: errorInfo?.componentStack, url: window.location.href, userAgent: navigator.userAgent, timestamp: new Date().toISOString(), }; try { await fetch('/api/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } catch (reportError) { console.error('Failed to report error:', reportError); } } } class ReportingErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { ErrorReportingService.report(error, errorInfo); } render() { if (this.state.hasError) { return ; } return this.props.children; } } ``` ### 7.9 Retry Mechanisms **Simple Retry:** ```jsx function RetryableComponent() { const [error, setError] = useState(null); const [retryCount, setRetryCount] = useState(0); const handleRetry = () => { setError(null); setRetryCount(prev => prev + 1); }; return (

Error occurred

{error?.message}

} onError={setError} >
); } ``` **Exponential Backoff Retry:** ```jsx function ExponentialRetryComponent() { const [error, setError] = useState(null); const [retryCount, setRetryCount] = useState(0); const [isRetrying, setIsRetrying] = useState(false); const handleRetry = async () => { setIsRetrying(true); setError(null); // Exponential backoff const delay = Math.min(1000 * Math.pow(2, retryCount), 30000); await new Promise(resolve => setTimeout(resolve, delay)); setRetryCount(prev => prev + 1); setIsRetrying(false); }; return (

Error occurred

{error?.message}

{isRetrying ? ( ) : ( )} } onError={setError} >
); } ``` **Smart Retry:** ```jsx function SmartRetryComponent() { const [error, setError] = useState(null); const [retryCount, setRetryCount] = useState(0); const maxRetries = 3; const shouldRetry = error => { // Only retry on specific errors const retryableErrors = [ 'NetworkError', 'TimeoutError', 'ServiceUnavailable', ]; return retryableErrors.some(type => error?.name?.includes(type)); }; const handleRetry = () => { setError(null); setRetryCount(prev => prev + 1); }; return (

Error occurred

{error?.message}

{shouldRetry(error) && retryCount < maxRetries ? ( ) : (

Please try again later or contact support.

)} } onError={setError} >
); } ``` ### 7.10 Error Boundaries with React Query/SWR **React Query Error Boundaries:** ```jsx import { useQuery, QueryErrorResetBoundary } from '@tanstack/react-query'; function UserProfile() { const { data, error, isLoading } = useQuery({ queryKey: ['user', 'profile'], queryFn: fetchUserProfile, retry: 3, }); if (isLoading) return ; if (error) return ; return ; } // With Error Boundary function App() { return ( }> ); } ``` **SWR Error Boundaries:** ```jsx import useSWR from 'swr'; function UserProfile() { const { data, error, isLoading } = useSWR('/api/user/profile', fetcher); if (isLoading) return ; if (error) return ; return ; } // With Error Boundary function App() { return ( }> ); } function handleError(error) { // Log error console.error('SWR Error:', error); // Show notification toast.error('Failed to load data'); } ``` **Combined Approach:** ```jsx function DataComponent() { const { data, error, isLoading, refetch } = useQuery({ queryKey: ['data'], queryFn: fetchData, }); if (isLoading) return ; if (error) { // Let error boundary handle it throw error; } return ; } function App() { return ( window.location.reload()} /> } > ); } ``` ### 7.11 Error Boundaries with Suspense **Suspense + Error Boundary Pattern:** ```jsx import { Suspense } from 'react'; function ResourceComponent() { const resource = useResource(resourcePromise); if (resource.status === 'pending') { throw resource.promise; // Suspense catches this } if (resource.status === 'rejected') { throw resource.reason; // Error Boundary catches this } return ; } function App() { return ( }> }> ); } ``` **Multiple Suspense Boundaries:** ```jsx function App() { return ( }> }>
}> }> }> }>