import React from "react"; import { View } from "react-native"; import { Text } from "./text"; import { Button } from "./button"; interface Props { children: React.ReactNode; fallback?: React.ReactNode; /** * Optional callback fired when the user taps "Try Again". Use this * to clear any module-level state that contributed to the crash * (vault: `queryClient.clear()`, `KeyManager.lock()`, etc.). * Without this, a re-render against still-broken module state * would crash-loop the user with no escape. */ onReset?: () => void; } interface State { hasError: boolean; /** Bumped on every reset so children remount fresh — clears React * state inside the crashing subtree, not just the boundary's own * flag. Without this, "Try Again" only resets `hasError` and a * child whose component state is the root cause re-throws on the * next render. */ resetKey: number; } export class ErrorBoundary extends React.Component { override state: State = { hasError: false, resetKey: 0 }; static getDerivedStateFromError(): Partial { return { hasError: true }; } override componentDidCatch(error: Error, info: React.ErrorInfo) { if (__DEV__) { console.error("[ErrorBoundary]", error, info.componentStack); } } private handleReset = (): void => { try { this.props.onReset?.(); } catch { // onReset failures must not crash the boundary itself. } this.setState((prev) => ({ hasError: false, resetKey: prev.resetKey + 1 })); }; override render() { if (this.state.hasError) { return ( this.props.fallback ?? ( Something went wrong ) ); } // Keying the subtree on `resetKey` forces a remount on retry so // component-level state that contributed to the crash is // recreated from scratch rather than re-rendering against the // same stale props/state and crash-looping. return {this.props.children}; } }