git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/error-boundaries-react" ~/.claude/skills/majiayu000-claude-skill-registry-error-boundaries-react && rm -rf "$T"
skills/data/error-boundaries-react/SKILL.mdError 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 (ภาษาไทย)
ผลกระทบทางธุรกิจ:
-
ลด Downtime - Error Boundaries ช่วยลดเวลาที่แอปพลิเคชันไม่สามารถใช้งานได้เนื่องจากข้อผิดพลาด โดยการจับและจัดการข้อผิดพลาดในระดับ component แทนที่จะทำให้ทั้งแอป crash
-
เพิ่ม User Retention - ผู้ใช้มักจะออกจากแอปพลิเคชันที่มีข้อผิดพลาดบ่อย Error Boundaries ช่วยให้แอปพลิเคชันยังคงใช้งานได้แม้จะมีข้อผิดพลาดในบางส่วน
-
ลด Support Cost - Error Reporting ที่ดีช่วยให้ทีมพัฒนาสามารถระบุและแก้ไขปัญหาได้เร็วขึ้น ทำให้ลดคำถามและปัญหาจากผู้ใช้
-
เพิ่ม Trust - แอปพลิเคชันที่มีการจัดการข้อผิดพลาดที่ดีสร้างความไว้วางใจให้กับผู้ใช้
-
ปรับปรุง User Experience - Fallback UI ที่ดีช่วยให้ผู้ใช้เข้าใจสถานการณ์และมีตัวเลือกในการกู้คืน
1.3 Product Thinking (ภาษาไทย)
มุมมองด้านผลิตภัณฑ์:
-
Graceful Degradation - Error Boundaries ช่วยให้แอปพลิเคชันสามารถทำงานได้แม้จะมีบางส่วนที่ไม่ทำงาน ผู้ใช้ยังสามารถใช้งานส่วนอื่นๆ ของแอปพลิเคชันได้
-
Error Recovery - ให้ผู้ใช้มีตัวเลือกในการกู้คืนจากข้อผิดพลาด เช่น Retry, Go Home, Contact Support
-
Contextual Error Messages - Error messages ต้องเข้าใจง่ายและให้ข้อมูลที่เป็นประโยชน์แก่ผู้ใช้
-
Error Tracking - Error reporting ต้องมี context เพียงพอสำหรับการ debug เช่น component stack, user info, environment
-
User-Centric Design - Fallback UI ต้องออกแบบให้ใช้งานง่ายและให้ผู้ใช้มีความรู้สึกดีแม้จะเกิดข้อผิดพลาด
2. Technical Deep Dive (The "How-to")
2.1 Core Logic
Error Boundaries คือ React components ที่:
- จับ JavaScript errors ใน component tree ของตนเอง
- Log errors สำหรับการ debug
- แสดง 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
// 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<Props, State> { 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 ( <div className="error-boundary"> <h2>Something went wrong</h2> {this.state.error && ( <p className="error-message">{this.state.error.message}</p> )} <button onClick={this.resetError}>Try Again</button> </div> ) } return this.props.children } }
Step 2: Create Error Reporting Service
// services/ErrorReportingService.ts import * as Sentry from '@sentry/react' export class ErrorReportingService { static async report(error: Error, errorInfo?: ErrorInfo, context?: Record<string, any>) { // 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
// components/ErrorFallback.tsx import React from 'react' interface SimpleFallbackProps { error?: Error | null onReset?: () => void } export function SimpleFallback({ error, onReset }: SimpleFallbackProps) { return ( <div className="error-boundary simple"> <h2>Something went wrong</h2> <p>We're sorry for the inconvenience.</p> {error && <p className="error-message">{error.message}</p>} {onReset && <button onClick={onReset}>Try Again</button>} </div> ) } interface DetailedFallbackProps { error?: Error | null errorInfo?: ErrorInfo | null onReset?: () => void eventId?: string } export function DetailedFallback({ error, errorInfo, onReset, eventId }: DetailedFallbackProps) { return ( <div className="error-boundary detailed"> <div className="error-icon">⚠️</div> <h2>Oops! Something went wrong</h2> <div className="error-details"> <h3>Error Message</h3> <p>{error?.message || 'Unknown error'}</p> {errorInfo && ( <details> <summary>Technical Details</summary> <pre>{errorInfo.componentStack}</pre> </details> )} </div> <div className="error-actions"> {onReset && ( <button onClick={onReset} className="btn-primary"> Try Again </button> )} <button onClick={() => window.location.href = '/'} className="btn-secondary"> Go Home </button> <button onClick={() => window.location.reload()} className="btn-tertiary"> Reload Page </button> </div> {eventId && ( <p className="error-event-id"> Error ID: <code>{eventId}</code> </p> )} <p className="error-contact"> Need help? <a href="/support">Contact Support</a> </p> </div> ) }
Step 4: Implement Placement Strategy
// 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 <ErrorBoundary fallback={<DetailedFallback />} onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'global', timestamp: new Date().toISOString(), }) }} > <div className="app-layout"> <Header /> <main> {/* Feature-specific boundaries */} <ErrorBoundary fallback={ <DetailedFallback error={null} onReset={() => window.location.href = '/dashboard'} /> } onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'dashboard', feature: 'dashboard', }) }} > <Dashboard /> </ErrorBoundary> <ErrorBoundary fallback={ <DetailedFallback error={null} onReset={() => window.location.href = '/projects'} /> } onError={(error, errorInfo) => { ErrorReportingService.report(error, errorInfo, { location: 'projects', feature: 'projects', }) }} > <Projects /> </ErrorBoundary> </main> <Footer /> </div> </ErrorBoundary> ) }
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:
// 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:
npm install react-error-boundary
// 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 ( <ErrorBoundary FallbackComponent={DetailedFallback} onError={logError} onReset={() => { // Optional: reset app state window.location.href = '/' }} > {children} </ErrorBoundary> ) }
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 ต้องปฏิบัติตามหลักความปลอดภัย:
- No Sensitive Data in Errors - ไม่แสดงข้อมูลที่ละเอียดอ่อนใน error messages
- Error Logging - Log errors อย่างปลอดภัยโดยไม่เก็บข้อมูลส่วนบุคคล
- Sanitization - Sanitize error messages ก่อนแสดงใน UI
- Rate Limiting - จำกัดจำนวน errors ที่ส่งไปยัง error tracking service
// 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 ต้องสามารถอธิบายได้ว่า:
- Error Context - Error เกิดขึ้นที่ไหนและเมื่อไร
- User Impact - Error นี้ส่งผลกระทบต่อผู้ใช้อย่างไร
- Recovery Path - ผู้ใช้สามารถกู้คืนจาก error ได้อย่างไร
- 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:
- Error Rate - จำนวน errors ต่อจำนวน requests
- Crash Rate - จำนวน crashes ต่อจำนวน sessions
- Recovery Rate - อัตราการกู้คืนจาก errors
- Error Reporting Latency - เวลาในการ report error
Business Metrics:
- User Retention - อัตราการกลับมาใช้งานหลังจากเกิด error
- Support Tickets - จำนวน support tickets ที่เกี่ยวข้องกับ errors
- User Satisfaction - ความพึงพอใจของผู้ใช้หลังจากเกิด error
- 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
- Over-Engineering - Error boundaries มากเกินไปทำให้โค้ดซับซ้อน
- Ignoring Non-Catchable Errors - ไม่จัดการ async errors และ event handler errors
- Poor Fallback UI - Fallback UI ที่ไม่เป็นประโยชน์สำหรับผู้ใช้
- Missing Error Context - Error reporting ไม่มี context เพียงพอ
- 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
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 <FallbackUI error={this.state.error} />; } return this.props.children; } }
7.2 Error Boundary with componentDidCatch
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 <FallbackUI error={this.state.error} info={this.state.errorInfo} />; } 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
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 <ErrorBoundary fallback={({ error, resetError }) => ( <div className="error-fallback"> <h2>Something went wrong</h2> <p>{error?.message}</p> <button onClick={resetError}>Try Again</button> </div> )} > <MyComponent /> </ErrorBoundary>
7.4 Error Boundary Limitations
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 <button onClick={this.handleClick}>Click me</button>; } }
7.5 Handling Non-Catchable Errors
// 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 <ErrorUI error={this.state.error} />; } if (!this.state.data) { return <LoadingUI />; } return <DataUI data={this.state.data} />; } }
7.6 Placement Strategies
Granular (Feature-Level):
function App() { return ( <div> <ErrorBoundary fallback={<ProfileError />}> <ProfileSection /> </ErrorBoundary> <ErrorBoundary fallback={<FeedError />}> <FeedSection /> </ErrorBoundary> <ErrorBoundary fallback={<SettingsError />}> <SettingsSection /> </ErrorBoundary> </div> ); }
Global (App-Level):
function App() { return ( <ErrorBoundary fallback={<GlobalError />}> <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/profile" element={<Profile />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Router> </ErrorBoundary> ); }
Hybrid Approach:
function App() { return ( // Global boundary for catastrophic errors <ErrorBoundary fallback={<GlobalError />}> <div className="app-layout"> <Header /> <main> {/* Feature-specific boundaries */} <ErrorBoundary fallback={<DashboardError />}> <Dashboard /> </ErrorBoundary> <ErrorBoundary fallback={<ProjectsError />}> <Projects /> </ErrorBoundary> </main> <Footer /> </div> </ErrorBoundary> ); }
7.7 Fallback UI Patterns
Simple Fallback:
function SimpleFallback({ error, resetError }) { return ( <div className="error-boundary"> <h2>Something went wrong</h2> <p>We're sorry for inconvenience.</p> {error && <p className="error-message">{error.message}</p>} <button onClick={resetError}>Try Again</button> </div> ); }
Detailed Fallback:
function DetailedFallback({ error, errorInfo, resetError }) { return ( <div className="error-boundary detailed"> <div className="error-icon">⚠️</div> <h2>Oops! Something went wrong</h2> <div className="error-details"> <h3>Error Message</h3> <p>{error?.message || 'Unknown error'}</p> {errorInfo && ( <details> <summary>Technical Details</summary> <pre>{errorInfo.componentStack}</pre> </details> )} </div> <div className="error-actions"> <button onClick={resetError} className="btn-primary"> Try Again </button> <button onClick={() => window.location.href = '/'} className="btn-secondary"> Go Home </button> <button onClick={() => window.location.reload()} className="btn-tertiary"> Reload Page </button> </div> <p className="error-contact"> Need help? <a href="/support">Contact Support</a> </p> </div> ); }
Themed Fallback:
function ThemedFallback({ error, resetError }) { const theme = useTheme(); return ( <div className={`error-boundary ${theme}`}> <div className="error-container"> <div className="error-illustration"> {theme === 'dark' ? ( <DarkErrorIllustration /> ) : ( <LightErrorIllustration /> )} </div> <h2>Something went wrong</h2> <p>{error?.message}</p> <button onClick={resetError} className="btn"> Try Again </button> </div> </div> ); }
7.8 Error Reporting Integration
Sentry Integration:
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 ( <FallbackUI eventId={this.state.eventId} resetError={() => 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:
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 <FallbackUI />; } 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:
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 <FallbackUI />; } return this.props.children; } }
7.9 Retry Mechanisms
Simple Retry:
function RetryableComponent() { const [error, setError] = useState(null); const [retryCount, setRetryCount] = useState(0); const handleRetry = () => { setError(null); setRetryCount(prev => prev + 1); }; return ( <ErrorBoundary fallback={ <div className="error-fallback"> <h2>Error occurred</h2> <p>{error?.message}</p> <button onClick={handleRetry}> Retry ({retryCount}) </button> </div> } onError={setError} > <ChildComponent key={retryCount} /> </ErrorBoundary> ); }
Exponential Backoff Retry:
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 ( <ErrorBoundary fallback={ <div className="error-fallback"> <h2>Error occurred</h2> <p>{error?.message}</p> {isRetrying ? ( <button disabled>Retrying...</button> ) : ( <button onClick={handleRetry}> Retry ({retryCount}) </button> )} </div> } onError={setError} > <ChildComponent key={retryCount} /> </ErrorBoundary> ); }
Smart Retry:
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 ( <ErrorBoundary fallback={ <div className="error-fallback"> <h2>Error occurred</h2> <p>{error?.message}</p> {shouldRetry(error) && retryCount < maxRetries ? ( <button onClick={handleRetry}> Retry ({retryCount}/{maxRetries}) </button> ) : ( <p>Please try again later or contact support.</p> )} </div> } onError={setError} > <ChildComponent key={retryCount} /> </ErrorBoundary> ); }
7.10 Error Boundaries with React Query/SWR
React Query Error Boundaries:
import { useQuery, QueryErrorResetBoundary } from '@tanstack/react-query'; function UserProfile() { const { data, error, isLoading } = useQuery({ queryKey: ['user', 'profile'], queryFn: fetchUserProfile, retry: 3, }); if (isLoading) return <Loading />; if (error) return <ErrorUI error={error} />; return <Profile data={data} />; } // With Error Boundary function App() { return ( <ErrorBoundary fallback={<GlobalError />}> <QueryErrorResetBoundary> <UserProfile /> </QueryErrorResetBoundary> </ErrorBoundary> ); }
SWR Error Boundaries:
import useSWR from 'swr'; function UserProfile() { const { data, error, isLoading } = useSWR('/api/user/profile', fetcher); if (isLoading) return <Loading />; if (error) return <ErrorUI error={error} />; return <Profile data={data} />; } // With Error Boundary function App() { return ( <ErrorBoundary fallback={<GlobalError />}> <SWRConfig value={{ onError: handleError }}> <UserProfile /> </SWRConfig> </ErrorBoundary> ); } function handleError(error) { // Log error console.error('SWR Error:', error); // Show notification toast.error('Failed to load data'); }
Combined Approach:
function DataComponent() { const { data, error, isLoading, refetch } = useQuery({ queryKey: ['data'], queryFn: fetchData, }); if (isLoading) return <Loading />; if (error) { // Let error boundary handle it throw error; } return <DataView data={data} />; } function App() { return ( <ErrorBoundary fallback={ <ErrorFallback onRetry={() => window.location.reload()} /> } > <DataComponent /> </ErrorBoundary> ); }
7.11 Error Boundaries with Suspense
Suspense + Error Boundary Pattern:
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 <DataView data={resource.result} />; } function App() { return ( <ErrorBoundary fallback={<ErrorFallback />}> <Suspense fallback={<Loading />}> <ResourceComponent /> </Suspense> </ErrorBoundary> ); }
Multiple Suspense Boundaries:
function App() { return ( <ErrorBoundary fallback={<GlobalError />}> <Suspense fallback={<PageLoading />}> <Header /> <main> <ErrorBoundary fallback={<ContentError />}> <Suspense fallback={<SectionLoading />}> <Dashboard /> </Suspense> </ErrorBoundary> <ErrorBoundary fallback={<ContentError />}> <Suspense fallback={<SectionLoading />}> <Feed /> </Suspense> </ErrorBoundary> </main> <Footer /> </Suspense> </ErrorBoundary> ); }
7.12 Error Boundaries in Server Components (Next.js)
Next.js App Router:
// app/error.js 'use client'; export default function Error({ error, reset, }) { return ( <div className="error-page"> <h2>Something went wrong!</h2> <p>{error.message}</p> <button onClick={reset}>Try again</button> </div> ); } // app/global-error.js 'use client'; export default function GlobalError({ error }) { return ( <html> <body> <div className="global-error"> <h2>Application Error</h2> <p>{error.message}</p> <button onClick={() => window.location.reload()}> Reload Application </button> </div> </body> </html> ); }
Next.js Pages Router:
// pages/_error.js class ErrorPage extends React.Component { static getInitialProps({ res, err }) { const statusCode = res ? res.statusCode : err ? err.statusCode : 404; return { statusCode }; } render() { const { statusCode } = this.props; return ( <div className="error-page"> <h1>{statusCode}</h1> <p>Something went wrong</p> <Link href="/">Go Home</Link> </div> ); } } export default ErrorPage;
Component-Level Error Boundary:
'use client'; class ComponentErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('Error in component:', error, errorInfo); } render() { if (this.state.hasError) { return <ErrorFallback />; } return this.props.children; } } // Usage in Next.js component export default function MyComponent() { return ( <ComponentErrorBoundary> <Content /> </ComponentErrorBoundary> ); }
7.13 react-error-boundary Library
Installation:
npm install react-error-boundary
Basic Usage:
import { ErrorBoundary } from 'react-error-boundary'; function App() { return ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={(error, errorInfo) => { console.error('Error:', error, errorInfo); }} > <MyComponent /> </ErrorBoundary> ); } function ErrorFallback({ error, resetErrorBoundary }) { return ( <div className="error-fallback"> <h2>Something went wrong</h2> <p>{error.message}</p> <button onClick={resetErrorBoundary}> Try Again </button> </div> ); }
withErrorBoundary HOC:
import { withErrorBoundary } from 'react-error-boundary'; class MyComponent extends React.Component { render() { return <div>{this.props.children}</div>; } } export default withErrorBoundary(MyComponent); // Or with custom fallback export default withErrorBoundary(MyComponent, { FallbackComponent: CustomFallback, onError: (error, errorInfo) => { logError(error, errorInfo); }, });
ErrorBoundaryType:
import { ErrorBoundary, ErrorBoundaryType } from 'react-error-boundary'; function App() { return ( <ErrorBoundary type={ErrorBoundaryType.Component} FallbackComponent={ComponentFallback} > <MyComponent /> </ErrorBoundary> ); } function ComponentFallback({ error, resetErrorBoundary }) { return <div>Component error: {error.message}</div>; }
7.14 Testing Error Boundaries
Unit Testing:
import { render, screen } from '@testing-library/react'; import ErrorBoundary from './ErrorBoundary'; describe('ErrorBoundary', () => { it('catches errors and renders fallback', () => { const ThrowError = () => { throw new Error('Test error'); }; render( <ErrorBoundary fallback={<FallbackUI />}> <ThrowError /> </ErrorBoundary> ); expect(screen.getByText('Error occurred')).toBeInTheDocument(); }); it('calls onError callback', () => { const onError = jest.fn(); const ThrowError = () => { throw new Error('Test error'); }; render( <ErrorBoundary fallback={<FallbackUI />} onError={onError}> <ThrowError /> </ErrorBoundary> ); expect(onError).toHaveBeenCalled(); expect(onError.mock.calls[0][0]).toBeInstanceOf(Error); }); });
Testing with React Testing Library:
import { render, screen } from '@testing-library/react'; describe('ErrorBoundary Integration', () => { it('renders fallback when child throws error', () => { const ThrowComponent = () => { throw new Error('Test error'); }; render( <ErrorBoundary fallback={<FallbackUI />}> <ThrowComponent /> </ErrorBoundary> ); expect(screen.getByText(/something went wrong/i)).toBeInTheDocument(); }); it('renders children when no error', () => { const SafeComponent = () => <div>Safe content</div>; render( <ErrorBoundary fallback={<FallbackUI />}> <SafeComponent /> </ErrorBoundary> ); expect(screen.getByText('Safe content')).toBeInTheDocument(); }); });
7.15 Error Boundary Composition
Nested Boundaries:
function App() { return ( // Global boundary <ErrorBoundary fallback={<GlobalFallback />}> <div className="app"> <Header /> <main> {/* Feature boundaries */} <ErrorBoundary fallback={<DashboardFallback />}> <Dashboard /> </ErrorBoundary> <ErrorBoundary fallback={<ProjectsFallback />}> <Projects /> </ErrorBoundary> <ErrorBoundary fallback={<SettingsFallback />}> <Settings /> </ErrorBoundary> </main> <Footer /> </div> </ErrorBoundary> ); }
Boundary Hierarchy:
// Outer boundary catches everything <ErrorBoundary fallback={<AppFallback />}> <AppLayout> {/* Inner boundaries for specific features */} <ErrorBoundary fallback={<DashboardFallback />}> <Dashboard /> </ErrorBoundary> <ErrorBoundary fallback={<ProjectsFallback />}> <Projects /> </ErrorBoundary> </AppLayout> </ErrorBoundary>
7.16 Recovering from Errors
State Reset:
class RecoverableErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { // Log error logError(error, errorInfo); } resetError = () => { this.setState({ hasError: false, error: null }); }; render() { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} onReset={this.resetError} /> ); } return this.props.children; } }
Route Reset:
function ErrorFallback({ error, onReset }) { const navigate = useNavigate(); const handleReset = () => { // Reset to home or previous route navigate('/'); onReset(); }; return ( <div className="error-fallback"> <h2>Something went wrong</h2> <p>{error?.message}</p> <button onClick={handleReset}>Go Home</button> </div> ); }
Data Refresh:
function RefreshableErrorBoundary({ children, fallback }) { const [error, setError] = useState(null); const [retryKey, setRetryKey] = useState(0); const handleRefresh = () => { setError(null); setRetryKey(prev => prev + 1); }; return ( <ErrorBoundary fallback={ <div className="error-fallback"> <h2>Error occurred</h2> <p>{error?.message}</p> <button onClick={handleRefresh}>Refresh</button> </div> } onError={setError} > <React.Fragment key={retryKey}> {children} </React.Fragment> </ErrorBoundary> ); }
7.17 User-Friendly Error Messages
Contextual Messages:
function ContextualFallback({ error, component }) { const getErrorMessage = () => { switch (component) { case 'Dashboard': return 'We couldn\'t load your dashboard. Please try again.'; case 'Profile': return 'We couldn\'t load your profile. Please try again.'; case 'Settings': return 'We couldn\'t save your settings. Please try again.'; default: return 'Something went wrong. Please try again.'; } }; return ( <div className="error-fallback"> <h2>Oops!</h2> <p>{getErrorMessage()}</p> {error?.message && ( <details> <summary>Technical details</summary> <p>{error.message}</p> </details> )} </div> ); }
Action-Oriented Messages:
function ActionFallback({ error, onRetry, onGoHome, onContact }) { return ( <div className="error-fallback"> <div className="error-icon">⚠️</div> <h2>Something went wrong</h2> <p>We're sorry for inconvenience.</p> <div className="error-actions"> <button onClick={onRetry} className="btn-primary"> Try Again </button> <button onClick={onGoHome} className="btn-secondary"> Go Home </button> <button onClick={onContact} className="btn-tertiary"> Contact Support </button> </div> </div> ); }
7.18 Common Patterns
Page-Level Boundaries:
function PageWrapper({ children, pageName }) { return ( <ErrorBoundary fallback={<PageFallback pageName={pageName} />} onError={(error) => logPageError(pageName, error)} > {children} </ErrorBoundary> ); } // Usage <PageWrapper pageName="Dashboard"> <Dashboard /> </PageWrapper>
Component-Level Boundaries:
function ComponentWrapper({ children, componentName }) { return ( <ErrorBoundary fallback={<ComponentFallback componentName={componentName} />} onError={(error) => logComponentError(componentName, error)} > {children} </ErrorBoundary> ); } // Usage <ComponentWrapper componentName="UserProfile"> <UserProfile /> </ComponentWrapper>
Feature-Level Boundaries:
function FeatureWrapper({ children, featureName }) { return ( <ErrorBoundary fallback={<FeatureFallback featureName={featureName} />} onError={(error) => logFeatureError(featureName, error)} > <Suspense fallback={<FeatureLoading featureName={featureName} />}> {children} </Suspense> </ErrorBoundary> ); } // Usage <FeatureWrapper featureName="Dashboard"> <Dashboard /> </FeatureWrapper>
7.19 Monitoring and Alerting
Error Rate Monitoring:
class ErrorRateMonitor { constructor() { this.errors = []; this.startTime = Date.now(); this.threshold = 10; // 10 errors per minute } recordError(error) { const now = Date.now(); this.errors.push({ error, timestamp: now }); // Clean old errors (older than 1 minute) this.errors = this.errors.filter(e => now - e.timestamp < 60000); // Check threshold if (this.errors.length >= this.threshold) { this.alertHighErrorRate(); } } alertHighErrorRate() { console.error('High error rate detected!'); // Send alert to monitoring service sendAlert('High error rate', { count: this.errors.length, duration: Date.now() - this.startTime, }); } } const monitor = new ErrorRateMonitor(); class MonitoredErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { monitor.recordError(error); logError(error, errorInfo); } render() { if (this.state.hasError) { return <FallbackUI />; } return this.props.children; } }
8. Related Skills
02-frontend/react-best-practices02-frontend/nextjs-patterns14-monitoring-observability/error-tracking01-foundations/code-review