error-boundaries-in-react
Implementing error boundaries for graceful error handling and improved user experience in React applications.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
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 (ภาษาไทย)
ผลกระทบทางธุรกิจ:
-
ลด 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-practices -
02-frontend/nextjs-patterns -
14-monitoring-observability/error-tracking -
01-foundations/code-review
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!