Error Boundaries Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
Know the two layers: try/catch for async work and event handlers; error boundaries for render and lifecycle throws - neither replaces the other.
Wrap major screens, not only the root: One leaf crash should not white-screen the entire app - per-route boundaries contain blast radius.
Keep a root boundary as last resort: Catches provider failures and navigation shell bugs - still log and offer restart, not a blank screen.
Use class boundaries or react-error-boundary: Functional components cannot implement getDerivedStateFromError - do not fake boundaries with hooks.
Log in componentDidCatch always: Production crashes without telemetry are invisible - forward to Sentry or your reporter before calling reset.
Never show stack traces to users: Fallback UI gets actionable copy; engineers read logs - error.message leaks implementation detail.
Reset with remount or refetch: reset() alone re-renders the same corrupt state - bump a key on children or refetch screen queries in onReset.
Try/catch inside onPress: Handler throws bypass boundaries - wrap native module and fetch calls and surface toasts or inline errors.
Distinguish offline from server errors: NetInfo false does not mean HTTP 500 - different copy and retry strategies for each.
Hide offline banner during reachability probe: isInternetReachable === null on launch is not offline - show banner only on confirmed disconnect.
Wire onlineManager to NetInfo: TanStack Query should pause retries offline - uncapped retries on airplane mode drain battery.
Label stale cached data: Show dataUpdatedAt or "Saved data from …" during SWR - users must know prices or balances may be old.
Persist failed mutations: Optimistic UI without a queue loses creates offline - AsyncStorage or MMKV replay with idempotency keys on reconnect.
Install global handler once at bootstrap: ErrorUtils.setGlobalHandler in root _layout before routes mount - chain to the previous handler after logging.
Treat isFatal seriously: Fatal JS errors may leave the bundle unhealthy - report immediately; do not only console.warn.
Catch unhandled promise rejections: Add onunhandledrejection (where supported) as a safety net beside setGlobalHandler.
Do not reload the app on Retry: Updates.reloadAsync() discards in-memory state - prefer refetch() or boundary reset.
Feature-flag risky screens: Remote kill switch plus local crash counter auto-disables a screen after repeated boundary trips.
Cache flag payloads with TTL: AsyncStorage defaults let the app boot when the flag service is down - stale-off is safer than stale-on for new crashes.
Model capability tiers: full / degraded / unavailable for biometrics, location, and camera - degrade with clear copy, do not throw.
Use requireOptionalNativeModule in Expo Go paths: Missing native classes should downgrade features - not crash at import time.
Map errors to user-facing categories: Network, auth, validation, and unknown each get distinct headline + action - one generic "Something went wrong" is a last resort.
Offer one primary recovery action: Retry, Go back, or Contact support - not three equal buttons that paralyze mobile users.
Keep boundary fallback JSX trivial: No data fetching inside the boundary component - throws in fallback escalate to the parent boundary.
Test airplane mode and boundary reset on device: Simulators hide NetInfo flapping and remount races - QA scripts should include offline + retry + kill-switch paths.
isError.componentDidCatch.Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026