/ components / ErrorBoundary.tsx
ErrorBoundary.tsx
  1  import React from 'react';
  2  import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
  3  import { Ionicons } from '@expo/vector-icons';
  4  import { Colors } from '@/constants/Colors';
  5  import { useTheme } from '@/constants/ThemeContext';
  6  
  7  interface ErrorBoundaryState {
  8    hasError: boolean;
  9    error?: Error | undefined;
 10    errorInfo?: React.ErrorInfo | undefined;
 11  }
 12  
 13  interface ErrorBoundaryProps {
 14    children: React.ReactNode;
 15    fallback?: React.ComponentType<{ error?: Error | undefined; resetError: () => void }>;
 16  }
 17  
 18  class ErrorBoundary extends React.Component<
 19    ErrorBoundaryProps,
 20    ErrorBoundaryState
 21  > {
 22    constructor(props: ErrorBoundaryProps) {
 23      super(props);
 24      this.state = { hasError: false };
 25    }
 26  
 27    static getDerivedStateFromError(error: Error): ErrorBoundaryState {
 28      return { hasError: true, error };
 29    }
 30  
 31    override componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
 32      console.error('ErrorBoundary caught an error:', error, errorInfo);
 33      this.setState({ error, errorInfo });
 34    }
 35  
 36    resetError = () => {
 37      this.setState({ hasError: false, error: undefined as Error | undefined, errorInfo: undefined as React.ErrorInfo | undefined });
 38    };
 39  
 40    override render() {
 41      if (this.state.hasError) {
 42        if (this.props.fallback) {
 43          const FallbackComponent = this.props.fallback;
 44          return (
 45            <FallbackComponent
 46              error={this.state.error as Error | undefined}
 47              resetError={this.resetError}
 48            />
 49          );
 50        }
 51  
 52        return (
 53          <DefaultErrorFallback
 54            error={this.state.error as Error | undefined}
 55            resetError={this.resetError}
 56          />
 57        );
 58      }
 59  
 60      return this.props.children;
 61    }
 62  }
 63  
 64  const DefaultErrorFallback: React.FC<{
 65    error?: Error | undefined;
 66    resetError: () => void;
 67  }> = ({ error, resetError }) => {
 68    const { actualTheme } = useTheme();
 69    const colors = Colors[actualTheme];
 70    const styles = getStyles(colors);
 71  
 72    return (
 73      <View style={styles.container}>
 74        <Ionicons
 75          name="alert-circle-outline"
 76          size={64}
 77          color={colors.notification}
 78        />
 79        <Text style={styles.title}>Something went wrong</Text>
 80        <Text style={styles.message}>
 81          {error?.message || 'An unexpected error occurred'}
 82        </Text>
 83        <TouchableOpacity style={styles.button} onPress={resetError}>
 84          <Text style={styles.buttonText}>Try Again</Text>
 85        </TouchableOpacity>
 86      </View>
 87    );
 88  };
 89  
 90  const getStyles = (colors: typeof Colors.light) =>
 91    StyleSheet.create({
 92      container: {
 93        flex: 1,
 94        justifyContent: 'center',
 95        alignItems: 'center',
 96        padding: 20,
 97        backgroundColor: colors.background,
 98      },
 99      title: {
100        fontSize: 24,
101        fontWeight: 'bold',
102        color: colors.text,
103        marginTop: 16,
104        marginBottom: 8,
105      },
106      message: {
107        fontSize: 16,
108        color: colors.text,
109        textAlign: 'center',
110        marginBottom: 24,
111        opacity: 0.8,
112      },
113      button: {
114        backgroundColor: colors.primary,
115        paddingHorizontal: 24,
116        paddingVertical: 12,
117        borderRadius: 8,
118      },
119      buttonText: {
120        color: '#FFFFFF',
121        fontSize: 16,
122        fontWeight: 'bold',
123      },
124    });
125  
126  export default ErrorBoundary;