React Native Error Handling
Every app meets failures: a lost connection, a server outage, an unexpected empty value, or a bug that slipped through testing. Users judge an app by how it behaves in those moments. A frozen screen or silent crash drives people away, while a clear message and a retry button keep trust intact.
Where Errors Come From
Source Example ──────────────────────────────────────────── Network No signal, timeout Server 500 reply, wrong data format User input Invalid email, empty field Device Permission denied, low storage Code bug Reading a field of undefined
Layer 1: try and catch
Wrap risky async work in try and catch. Use finally for steps that must run either way, such as hiding a spinner.
const loadProfile = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://example.com/profile');
if (!response.ok) {
throw new Error(`Server returned ${response.status}`);
}
setProfile(await response.json());
} catch (e) {
setError('Could not load your profile. Check your connection.');
} finally {
setLoading(false);
}
};
fetch does not throw on a 404 or 500 reply. Check response.ok and throw an error yourself.
Layer 2: Three Screen States
Every data screen has three possible faces: loading, error, and content. Design all three.
Loading Error Content ┌──────────┐ ┌──────────────┐ ┌──────────┐ │ ◌ │ │ Could not │ │ Item 1 │ │ Loading │ │ load data │ │ Item 2 │ │ │ │ [ Try again ]│ │ Item 3 │ └──────────┘ └──────────────┘ └──────────┘
if (loading) return <ActivityIndicator size="large" />;
if (error) {
return (
<View style={{ padding: 20, alignItems: 'center' }}>
<Text>{error}</Text>
<Button title="Try again" onPress={loadProfile} />
</View>
);
}
return <ProfileView data={profile} />;
Layer 3: Error Boundaries
A bug during rendering crashes the whole app. An error boundary catches the crash inside a section and shows a fallback instead. Error boundaries use a class component, since hooks have no equivalent.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
reportError(error, info); // send to a monitoring service
}
render() {
if (this.state.hasError) {
return (
<View style={{ padding: 20 }}>
<Text>Something went wrong.</Text>
<Button title="Retry" onPress={() => this.setState({ hasError: false })} />
</View>
);
}
return this.props.children;
}
}
<ErrorBoundary>
<ProfileScreen />
</ErrorBoundary>
App ├── ErrorBoundary ── Feed (crash shows fallback here only) ├── ErrorBoundary ── Profile └── ErrorBoundary ── Settings
Boundaries catch rendering errors only. They miss errors inside event handlers and async code, so keep try and catch there.
Layer 4: Global Handlers
Unhandled promise failures can slip through. Log them centrally so none vanish.
ErrorUtils.setGlobalHandler((error, isFatal) => {
reportError(error);
if (isFatal) {
Alert.alert('Unexpected error', 'Please restart the app.');
}
});
Layer 5: Monitoring Services
Crashes on user phones never appear in your terminal. Services such as Sentry and Firebase Crashlytics collect error reports with stack traces, device models, and app versions. Review them after every release.
Writing Helpful Messages
Unhelpful Helpful ─────────────────── ───────────────────────────────── "Error 500" "Our server is busy. Try again soon." "Network request failed" "No internet. Check your connection." "Invalid" "Enter an email like name@mail.com"
Offer a Way Forward
- Add a retry button to every failed load
- Keep previously loaded data visible when a refresh fails
- Save unsent form data so users never retype it
- Detect offline status with the NetInfo library and show a banner
Protect User Privacy in Reports
Remove passwords, tokens, and personal details before sending error logs. Reports need technical clues, not private data.
Summary
Strong error handling works in layers. Use try and catch for async work, design loading, error, and content states for each screen, wrap sections in error boundaries, and log unhandled failures centrally. Send reports to a monitoring service, write plain-language messages, and always give users a next step.
