diff --git a/App.tsx b/App.tsx index b6fa08d..143c1ed 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,5 @@ -import { useEffect, useState } from 'react'; +import { Component, useEffect, useState } from 'react'; +import type { ReactNode } from 'react'; import { ActivityIndicator, StatusBar, StyleSheet, Text, View } from 'react-native'; import * as SplashScreen from 'expo-splash-screen'; import { SafeAreaProvider } from 'react-native-safe-area-context'; @@ -11,6 +12,27 @@ import { colors } from './src/theme/colors'; SplashScreen.preventAutoHideAsync().catch(() => {}); +/** Tangkap error render agar tidak force-close: tampilkan layar aman. */ +class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; message: string }> { + state = { hasError: false, message: '' }; + + static getDerivedStateFromError(e: unknown) { + return { hasError: true, message: e instanceof Error ? e.message : String(e) }; + } + + render() { + if (this.state.hasError) { + return ( + + Terjadi kesalahan 😢 + {this.state.message} + + ); + } + return this.props.children; + } +} + export default function App() { const [ready, setReady] = useState(false); const [error, setError] = useState(null); @@ -62,16 +84,18 @@ export default function App() { } return ( - - - - - - + + + + + + + + ); } diff --git a/src/screens/DashboardScreen.tsx b/src/screens/DashboardScreen.tsx index 95f71fc..0ae4788 100644 --- a/src/screens/DashboardScreen.tsx +++ b/src/screens/DashboardScreen.tsx @@ -116,9 +116,7 @@ export function DashboardScreen() { useFocusEffect( useCallback(() => { let active = true; - load().then(() => { - if (!active) return; - }); + load().catch(() => {}); return () => { active = false; }; diff --git a/src/screens/HistoryScreen.tsx b/src/screens/HistoryScreen.tsx index 146fa7f..a58aaaf 100644 --- a/src/screens/HistoryScreen.tsx +++ b/src/screens/HistoryScreen.tsx @@ -60,9 +60,7 @@ export function HistoryScreen() { useFocusEffect( useCallback(() => { let active = true; - load().then(() => { - if (!active) return; - }); + load().catch(() => {}); return () => { active = false; }; diff --git a/src/screens/OnboardingScreen.tsx b/src/screens/OnboardingScreen.tsx index 6d1e49f..f321ab4 100644 --- a/src/screens/OnboardingScreen.tsx +++ b/src/screens/OnboardingScreen.tsx @@ -27,10 +27,15 @@ export function OnboardingScreen() { const [gender, setGender] = useState<'male' | 'female' | null>(null); const handleFinish = async () => { - await setSetting('user_name', name.trim()); - await setSetting('user_gender', gender ?? 'male'); - await setSetting('onboarded', '1'); - navigation.replace('Main'); + try { + await setSetting('user_name', name.trim()); + await setSetting('user_gender', gender ?? 'male'); + await setSetting('onboarded', '1'); + navigation.replace('Main'); + } catch { + // tetap lanjut walau gagal simpan + navigation.replace('Main'); + } }; return ( diff --git a/src/screens/SavingsScreen.tsx b/src/screens/SavingsScreen.tsx index 16c5135..53c2fee 100644 --- a/src/screens/SavingsScreen.tsx +++ b/src/screens/SavingsScreen.tsx @@ -69,9 +69,7 @@ export function SavingsScreen() { useFocusEffect( useCallback(() => { let active = true; - load().then(() => { - if (!active) return; - }); + load().catch(() => {}); return () => { active = false; }; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index c2ab3f6..893af66 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -102,15 +102,13 @@ export function SettingsScreen() { useFocusEffect( useCallback(() => { let active = true; - loadSettings().then(() => { - if (!active) return; - }); + loadSettings().catch(() => {}); getRecurring().then((r) => { if (active) setRecurring(r); - }); + }).catch(() => {}); getCategories().then((c) => { if (active) setCategories(c); - }); + }).catch(() => {}); return () => { active = false; };