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;
};