Error Boundaries
Error Boundaries
Error boundaries catch JavaScript errors in their child component tree.
Basic Error Boundary
// components/ErrorBoundary.jsx
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
// Log to error reporting service
console.error('Error Boundary caught:', error, errorInfo);
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<details>
<summary>Error Details</summary>
<pre>{this.state.error?.message}</pre>
<pre>{this.state.errorInfo?.componentStack}</pre>
</details>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
<ErrorBoundary
fallback={<CustomErrorScreen />}
onError={(error, info) => reportError(error, info)}
>
<App />
</ErrorBoundary>
Nested Error Boundaries
function App() {
return (
<ErrorBoundary fallback={<GlobalErrorScreen />}>
<Header />
<main>
<ErrorBoundary fallback={<ContentErrorScreen />}>
<Content />
</ErrorBoundary>
</main>
<ErrorBoundary fallback={<SidebarErrorScreen />}>
<Sidebar />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}
Error Boundary with Recovery
function RecoverableErrorBoundary({ children, maxRetries = 3 }) {
const [retryCount, setRetryCount] = useState(0);
const [hasError, setHasError] = useState(false);
const handleRetry = () => {
if (retryCount < maxRetries) {
setRetryCount(prev => prev + 1);
setHasError(false);
}
};
return (
<ErrorBoundary
fallback={
<div>
<p>Something went wrong</p>
{retryCount < maxRetries ? (
<button onClick={handleRetry}>
Retry ({maxRetries - retryCount} attempts left)
</button>
) : (
<button onClick={() => window.location.reload()}>
Reload Page
</button>
)}
</div>
}
>
{children}
</ErrorBoundary>
);
}
Global Error Handling
Global Error Handling
Handle unhandled errors and promise rejections at the application level.
Global Error Handler
// utils/errorHandler.js
export function setupGlobalErrorHandler() {
// Handle unhandled errors
window.onerror = (message, source, lineno, colno, error) => {
reportError({
type: 'uncaught-error',
message,
source,
lineno,
colno,
stack: error?.stack,
});
return false;
};
// Handle unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
reportError({
type: 'unhandled-rejection',
reason: event.reason?.message || event.reason,
stack: event.reason?.stack,
});
event.preventDefault();
});
}
// Call early in app initialization
setupGlobalErrorHandler();
Error Toast System
// context/ErrorToastContext.jsx
const ErrorToastContext = createContext(null);
export function ErrorToastProvider({ children }) {
const [errors, setErrors] = useState([]);
const showError = (error, options = {}) => {
const id = Date.now();
const toast = {
id,
message: error.message || 'An error occurred',
type: options.type || 'error',
duration: options.duration || 5000,
action: options.action,
};
setErrors(prev => [...prev, toast]);
if (toast.duration > 0) {
setTimeout(() => dismissError(id), toast.duration);
}
};
const dismissError = (id) => {
setErrors(prev => prev.filter(e => e.id !== id));
};
return (
<ErrorToastContext.Provider value={{ showError, dismissError }}>
{children}
<ErrorToastContainer errors={errors} onDismiss={dismissError} />
</ErrorToastContext.Provider>
);
}
// Hook
function useErrorToast() {
const context = useContext(ErrorToastContext);
if (!context) throw new Error('useErrorToast must be used within ErrorToastProvider');
return context;
}
// Component
function ErrorToastContainer({ errors, onDismiss }) {
return (
<div className="toast-container">
{errors.map(error => (
<div key={error.id} className={`toast toast-${error.type}`}>
<span>{error.message}</span>
{error.action && (
<button onClick={error.action.onClick}>
{error.action.label}
</button>
)}
<button onClick={() => onDismiss(error.id)}>×</button>
</div>
))}
</div>
);
}
Error Recovery Patterns
// Automatic retry with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response;
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(Math.pow(2, i) * 1000);
}
}
}
Error Reporting
Error Reporting
Implement comprehensive error tracking and monitoring.
Error Reporting Service
// services/errorReporting.js
class ErrorReportingService {
constructor(config) {
this.dsn = config.dsn;
this.environment = config.environment;
this.release = config.release;
this.user = null;
this.breadcrumbs = [];
}
setUser(user) {
this.user = user;
}
addBreadcrumb(message, category = 'default') {
this.breadcrumbs.push({
message,
category,
timestamp: new Date().toISOString(),
});
}
captureException(error, context = {}) {
const payload = {
exception: {
type: error.name,
value: error.message,
stacktrace: {
frames: this.parseStack(error.stack),
},
},
timestamp: new Date().toISOString(),
environment: this.environment,
release: this.release,
user: this.user,
contexts: context,
breadcrumbs: this.breadcrumbs.slice(-50),
};
this.send(payload);
}
captureMessage(message, level = 'info') {
const payload = {
message,
level,
timestamp: new Date().toISOString(),
environment: this.environment,
user: this.user,
};
this.send(payload);
}
parseStack(stack) {
// Parse stack trace into frames
return stack?.split('\n').map(line => ({
filename: line.match(/at (.+?) \\(/)?.[1],
lineno: parseInt(line.match(/:(\\d+):/)?.[1]),
function: line.match(/at (.+?)(?: \\()/)?.[1],
})) || [];
}
async send(payload) {
try {
await fetch(this.dsn, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
} catch (e) {
console.error('Failed to send error report:', e);
}
}
}
// Initialize
export const errorReporter = new ErrorReportingService({
dsn: process.env.ERROR_REPORTING_DSN,
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
});
React Integration
// hooks/useErrorReporting.js
function useErrorReporting() {
const { user } = useAuth();
useEffect(() => {
errorReporter.setUser(user);
}, [user]);
const reportError = useCallback((error, context = {}) => {
errorReporter.captureException(error, context);
}, []);
const reportMessage = useCallback((message, level = 'info') => {
errorReporter.captureMessage(message, level);
}, []);
const addBreadcrumb = useCallback((message, category) => {
errorReporter.addBreadcrumb(message, category);
}, []);
return { reportError, reportMessage, addBreadcrumb };
}
// Usage in components
function CheckoutPage() {
const { reportError, addBreadcrumb } = useErrorReporting();
const handlePayment = async () => {
addBreadcrumb('Payment initiated', 'payment');
try {
await processPayment();
} catch (error) {
reportError(error, { page: 'checkout', step: 'payment' });
showError('Payment failed. Please try again.');
}
};
}
Practice Problems
Create a reusable React component implementing Error Handling Architecture. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Error Handling Architecture using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Error Handling Architecture for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What can error boundaries catch?
2. Why should error reporting happen in componentDidCatch?
3. What should global error handlers capture?
4. What information should error reports include?
Flashcards
Question
What is an error boundary?
Click to reveal answer
Answer
A React component that catches JavaScript errors in its child component tree and displays a fallback UI.
Question
What should global error handlers capture?
Click to reveal answer
Answer
Uncaught errors (window.onerror) and unhandled promise rejections (unhandledrejection event).
Question
What are breadcrumbs in error reporting?
Click to reveal answer
Answer
A log of user actions leading up to an error, helping debug the sequence of events.
Question
Why use nested error boundaries?
Click to reveal answer
Answer
Question
What is Error Handling Architecture?
Click to reveal answer
Answer
Error Handling Architecture is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Error boundaries catch errors during rendering in the component tree
- 2.Use nested error boundaries to isolate errors to specific sections
- 3.Set up global handlers for uncaught errors and promise rejections
- 4.Error reports should include stack traces, context, and breadcrumbs
- 5.Implement retry mechanisms with exponential backoff
Interview Tips
- •Explain what error boundaries can and cannot catch
- •Describe a comprehensive error handling strategy
- •Know the difference between error boundaries and try-catch
Cheat Sheet
Error Handling Architecture Cheat Sheet
Error Boundary
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info);
}
}
Global Handlers
window.onerror = (msg, src, line, col, error) => { ... };
window.addEventListener('unhandledrejection', (e) => { ... });
Error Reporting
- Include stack trace
- Add user context
- Track breadcrumbs
- Set environment info