A lightweight, production-ready toolkit that improves stability and observability in React Native apps.
React Native apps fail silently in production for many common reasons:
| Problem | This toolkit |
|---|---|
| Unhandled async errors | safeAsync — wraps promises with timeout & error capture |
| Component render crashes | ReliabilityErrorBoundary — safe fallback UI + structured logging |
| Unstable network conditions | useNetworkHealth — detects offline / slow / unstable states |
| Silent promise rejections | safeAsync — global error callback |
| Background/foreground issues | useAppLifecycle — lifecycle state tracking |
| Crash loops | useCrashRecovery — detects repeated crashes on startup |
Works with React Native CLI and Expo. Every module is fully independent — adopt only what you need.
# npm
npm install react-native-reliability
# yarn
yarn add react-native-reliabilityInstall these only for the modules you use:
# useNetworkHealth
npm install @react-native-community/netinfo
# useCrashRecovery (persistent storage across restarts)
npm install @react-native-async-storage/async-storageimport { ReliabilityProvider } from 'react-native-reliability';
export default function App() {
return (
<ReliabilityProvider>
<MyApp />
</ReliabilityProvider>
);
}Individual hooks and components work without the provider using sensible defaults.
Catches React render-tree crashes and renders a fallback UI.
import { ReliabilityErrorBoundary } from 'react-native-reliability';
<ReliabilityErrorBoundary
fallback={<Text>Something went wrong. Please restart the app.</Text>}
onError={(error, info) => {
// send to crash analytics, record crash, etc.
logError(error, info.componentStack);
}}
>
<App />
</ReliabilityErrorBoundary>Props
| Prop | Type | Description |
|---|---|---|
fallback |
React.ReactNode |
UI shown when the tree crashes. Renders null if omitted. |
onError |
(error: Error, info: React.ErrorInfo) => void |
Called when a render error is caught. |
children |
React.ReactNode |
The component tree to protect. |
Detects degraded connectivity without constant polling.
import { useNetworkHealth } from 'react-native-reliability';
const network = useNetworkHealth();
switch (network.status) {
case 'offline': /* show offline banner */ break;
case 'slow': /* show slow-network warning */ break;
case 'unstable': /* retry with backoff */ break;
case 'healthy': /* proceed normally */ break;
}Requires @react-native-community/netinfo. Returns healthy (no-op) without it.
Returned object
| Field | Type | Description |
|---|---|---|
status |
'healthy' | 'slow' | 'unstable' | 'offline' |
Overall network status |
isOffline |
boolean |
No internet connection |
isSlow |
boolean |
High latency detected |
isUnstable |
boolean |
Frequent disconnect/reconnect |
Prevents unhandled promise rejections and adds timeout protection.
import { safeAsync } from 'react-native-reliability';
safeAsync(
() => fetchUser(userId),
{
timeout: 5000,
onError: (error) => logError(error),
}
)
.then(handleUser)
.catch(handleFetchError);Options
| Option | Type | Default | Description |
|---|---|---|---|
timeout |
number |
10000 |
Milliseconds before a TimeoutError is thrown |
onError |
(error: Error) => void |
— | Called for any error (including timeout) |
Tracks application foreground / background / inactive state transitions.
import { useAppLifecycle } from 'react-native-reliability';
const lifecycle = useAppLifecycle();
useEffect(() => {
if (lifecycle.state === 'background') {
stopNetworkPolling();
saveUserSession();
}
if (lifecycle.state === 'active') {
resumeNetworkPolling();
}
}, [lifecycle.state]);Returned object
| Field | Type | Description |
|---|---|---|
state |
'active' | 'background' | 'inactive' |
Current lifecycle state |
Detects repeated crash loops on startup and lets you recover gracefully.
import { useCrashRecovery, ReliabilityErrorBoundary } from 'react-native-reliability';
function Root() {
const crash = useCrashRecovery();
if (crash.hasRecentCrash) {
return (
<SafeModeScreen
crashCount={crash.crashCount}
onReset={crash.clearCrashHistory}
/>
);
}
return (
<ReliabilityErrorBoundary onError={crash.recordCrash}>
<App />
</ReliabilityErrorBoundary>
);
}Requires @react-native-async-storage/async-storage for persistence across restarts.
Falls back to in-memory storage (resets on every restart) when not installed.
Returned object
| Field | Type | Description |
|---|---|---|
hasRecentCrash |
boolean |
true when crashes ≥ crashThreshold within crashWindow |
crashCount |
number |
Number of crashes in the current window |
recordCrash |
(error?: Error) => Promise<void> |
Record a crash event |
clearCrashHistory |
() => Promise<void> |
Clear all stored crash data |
Override defaults globally with ReliabilityProvider:
<ReliabilityProvider
config={{
crashWindow: 120_000, // 2 minute window (default: 60_000)
crashThreshold: 3, // crashes to trigger hasRecentCrash (default: 2)
networkCheckUrl: 'https://api.myapp.com/health',
slowNetworkThreshold: 3000, // ms (default: 2000)
unstableNetworkThreshold: 5, // disconnects/min (default: 3)
}}
>
<App />
</ReliabilityProvider>All hooks also accept these values from context automatically when wrapped in the provider.
import React from 'react';
import {
ReliabilityProvider,
ReliabilityErrorBoundary,
useNetworkHealth,
useAppLifecycle,
useCrashRecovery,
safeAsync,
} from 'react-native-reliability';
function AppShell() {
const network = useNetworkHealth();
const lifecycle = useAppLifecycle();
const crash = useCrashRecovery();
// Show safe mode if crash loop detected
if (crash.hasRecentCrash) {
return <SafeModeScreen onReset={crash.clearCrashHistory} />;
}
return (
<ReliabilityErrorBoundary
onError={crash.recordCrash}
fallback={<ErrorScreen />}
>
{network.isOffline && <OfflineBanner />}
{lifecycle.state === 'active' && <MainApp />}
</ReliabilityErrorBoundary>
);
}
export default function App() {
return (
<ReliabilityProvider config={{ crashThreshold: 3 }}>
<AppShell />
</ReliabilityProvider>
);
}Stability, safety, and predictability — not analytics dashboards.
- Never crashes the host app — all handlers are wrapped in try/catch
- Minimal dependencies — peer deps are optional; modules work independently
- No polling — event-driven where possible; ping on demand
- Strict TypeScript — no
any, full type coverage - Works in dev and production — same API, no build flags
src/
core/
reliabilityProvider.tsx ← shared config context (optional)
modules/
errorBoundary/
ReliabilityErrorBoundary.tsx
networkHealth/
useNetworkHealth.ts
safeAsync/
safeAsync.ts
lifecycle/
useAppLifecycle.ts
crashRecovery/
useCrashRecovery.ts
hooks/
useNetworkHealth.ts ← re-exports
useAppLifecycle.ts
types/
reliabilityTypes.ts
index.ts
MIT