import React, { Component, ReactNode } from 'react'
interface Props {
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
}
interface State {
hasError: boolean
error: Error | null
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo)
// Log to error tracking service
this.props.onError?.(error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full bg-white shadow-lg rounded-lg p-6">
<div className="flex items-center gap-3 mb-4">
<i className="fas fa-exclamation-triangle text-red-500 text-2xl"></i>
<h2 className="text-xl font-bold">Something went wrong</h2>
</div>
<p className="text-gray-600 mb-4">
We're sorry for the inconvenience. Please try refreshing the page.
</p>
<button
onClick={() => window.location.reload()}
className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Refresh Page
</button>
</div>
</div>
)
)
}
return this.props.children
}
}
function App() {
return (
<ErrorBoundary onError={(error, errorInfo) => {
// Send to error tracking service
// logErrorToService(error, errorInfo)
}}>
<Router>
{/* App content */}
</Router>
</ErrorBoundary>
)
}
React error boundaries catch JavaScript errors in child components, log them, and display fallback UI instead of crashing the entire app. Since error boundaries must be class components in React, I create a generic ErrorBoundary wrapper and use it around route components or major features. The componentDidCatch lifecycle method receives the error and component stack trace for logging to error tracking services like Sentry. Error boundaries don't catch errors in event handlers, async code, or SSR—those need try/catch. I render different fallback UIs based on the error type: full-page errors for route-level failures, inline errors for widget failures. This defensive programming prevents one broken component from ruining the entire user experience.