Kantongku: hardening - ErrorBoundary + catch semua load async (anti force-close)
This commit is contained in:
@@ -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 (
|
||||
<View style={styles.center}>
|
||||
<Text style={styles.errorTitle}>Terjadi kesalahan 😢</Text>
|
||||
<Text style={styles.errorText}>{this.state.message}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [ready, setReady] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -62,6 +84,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SafeAreaProvider>
|
||||
<NavigationContainer>
|
||||
<RootNavigator initialRoute={initialRoute} />
|
||||
@@ -72,6 +95,7 @@ export default function App() {
|
||||
backgroundColor={colors.primary}
|
||||
/>
|
||||
</SafeAreaProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,7 @@ export function DashboardScreen() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
let active = true;
|
||||
load().then(() => {
|
||||
if (!active) return;
|
||||
});
|
||||
load().catch(() => {});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
|
||||
@@ -60,9 +60,7 @@ export function HistoryScreen() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
let active = true;
|
||||
load().then(() => {
|
||||
if (!active) return;
|
||||
});
|
||||
load().catch(() => {});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
|
||||
@@ -27,10 +27,15 @@ export function OnboardingScreen() {
|
||||
const [gender, setGender] = useState<'male' | 'female' | null>(null);
|
||||
|
||||
const handleFinish = async () => {
|
||||
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 (
|
||||
|
||||
@@ -69,9 +69,7 @@ export function SavingsScreen() {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
let active = true;
|
||||
load().then(() => {
|
||||
if (!active) return;
|
||||
});
|
||||
load().catch(() => {});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user