// src/components/ErrorBoundary.tsx import { Component, type ReactNode } from 'react' import { QueryErrorResetBoundary } from '@tanstack/react-query' /** * Props and State types */ type ErrorBoundaryProps = { children: ReactNode fallback?: (error: Error, reset: () => void) => ReactNode } type ErrorBoundaryState = { hasError: boolean error: Error | null } /** * React Error Boundary Class Component * * Required because error boundaries must be class components */ class ErrorBoundaryClass extends Component< ErrorBoundaryProps & { onReset?: () => void }, ErrorBoundaryState > { constructor(props: ErrorBoundaryProps & { onReset?: () => void }) { super(props) this.state = { hasError: false, error: null } } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // Log error to error reporting service console.error('Error caught by boundary:', error, errorInfo) // Example: Send to Sentry, LogRocket, etc. // Sentry.captureException(error, { contexts: { react: errorInfo } }) } handleReset = () => { // Call TanStack Query reset if provided this.props.onReset?.() // Reset error boundary state this.setState({ hasError: false, error: null }) } render() { if (this.state.hasError && this.state.error) { // Use custom fallback if provided if (this.props.fallback) { return this.props.fallback(this.state.error, this.handleReset) } // Default error UI return (

Something went wrong

Error details {this.state.error.message} {this.state.error.stack && (
                {this.state.error.stack}
              
)}
) } return this.props.children } } /** * Error Boundary with TanStack Query Reset * * Wraps components and catches errors thrown by queries * with throwOnError: true */ export function ErrorBoundary({ children, fallback }: ErrorBoundaryProps) { return ( {({ reset }) => ( {children} )} ) } /** * Usage Examples */ // Example 1: Wrap entire app export function AppWithErrorBoundary() { return ( ) } // Example 2: Wrap specific features export function UserProfileWithErrorBoundary() { return ( ) } // Example 3: Custom error UI export function CustomErrorBoundary({ children }: { children: ReactNode }) { return ( (

Oops!

We encountered an error: {error.message}

Go Home
)} > {children}
) } /** * Using throwOnError with Queries * * Queries can throw errors to error boundaries */ import { useQuery } from '@tanstack/react-query' // Example 1: Always throw errors function UserData({ id }: { id: number }) { const { data } = useQuery({ queryKey: ['user', id], queryFn: async () => { const response = await fetch(`/api/users/${id}`) if (!response.ok) throw new Error('User not found') return response.json() }, throwOnError: true, // Throw to error boundary }) return
{data.name}
} // Example 2: Conditional throwing (only server errors) function ConditionalErrorThrowing({ id }: { id: number }) { const { data } = useQuery({ queryKey: ['user', id], queryFn: async () => { const response = await fetch(`/api/users/${id}`) if (!response.ok) throw new Error(`HTTP ${response.status}`) return response.json() }, throwOnError: (error) => { // Only throw 5xx server errors to boundary // Handle 4xx client errors locally return error.message.includes('5') }, }) return
{data?.name ?? 'Not found'}
} /** * Multiple Error Boundaries (Layered) * * Place boundaries at different levels for granular error handling */ export function LayeredErrorBoundaries() { return ( // App-level boundary }>
{/* Feature-level boundary */} }> {/* Another feature boundary */}