All files / src/components ErrorBoundary.tsx

68.18% Statements 15/22
50% Branches 1/2
42.85% Functions 3/7
75% Lines 15/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                            13x 62x 62x 62x       62x 55x             55x     55x   55x 55x   55x 55x 55x       62x                               62x        
/**
 * @file ErrorBoundary.tsx
 * @description
 * Global error boundary component for catching and displaying application errors.
 *
 * **Features:**
 * - Catches unhandled JavaScript errors
 * - Catches unhandled promise rejections
 * - Displays user-friendly error messages
 * - Provides reload button for recovery
 * - Logs errors to console for debugging
 *
 * **Error Types Handled:**
 * - Runtime JavaScript errors
 * - Promise rejection errors
 * - Component render errors (indirectly)
 *
 * @component
 */
 
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
 
/**
 * ErrorBoundary component props
 * @interface ErrorBoundaryProps
 */
interface ErrorBoundaryProps {
  /** Child components to protect */
  children: React.ReactNode;
}
 
/**
 * Error boundary component capturing global errors
 * @component
 * @param {ErrorBoundaryProps} props - Component props
 * @returns {JSX.Element} Fallback UI on error or children if safe
 */
const ErrorBoundary: React.FC<ErrorBoundaryProps> = ({ children }) => {
  const [hasError, setHasError] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const { t } = useTranslation();
 
  // Attach error listeners for global error handling
  // Catches both synchronous errors and unhandled promise rejections
  useEffect(() => {
    const handleError = (error: Error) => {
      setHasError(true);
      setError(error);
      console.error('Error caught by ErrorBoundary:', error);
    };
 
    // 'error' event fires on synchronous runtime errors (e.g., undefined method calls)
    const errorHandler = (event: ErrorEvent) => handleError(event.error);
    
    // 'unhandledrejection' event fires for promises without .catch() or try/catch
    const rejectionHandler = (event: PromiseRejectionEvent) => handleError(event.reason);
 
    window.addEventListener('error', errorHandler);
    window.addEventListener('unhandledrejection', rejectionHandler);
 
    return () => {
      window.removeEventListener('error', errorHandler);
      window.removeEventListener('unhandledrejection', rejectionHandler);
    };
  }, []);
 
  Iif (hasError) {
    return (
      <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 text-red-500">
        <h1 className="text-3xl font-bold">{t('errorBoundary.title')}</h1>
        <p className="mt-4">{error?.message}</p>
 
        <button
          className="mt-6 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
          onClick={() => window.location.reload()}
        >
          {t('errorBoundary.reloadButton')}
        </button>
      </div>
    );
  }
 
  return <>{children}</>;
};
 
export default ErrorBoundary;