Error Handling
Learn comprehensive error handling strategies for API calls and network requests in React Native applications. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Error Handling Strategies
Implement robust error handling for network requests, including retry mechanisms, timeout handling, and user-friendly error messages.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Network Error Types
- Network errors - No internet connection — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Timeout errors - Request takes too long — a critical concept in cross-platform mobile development that you will use frequently in real projects
- HTTP errors - 4xx and 5xx status codes — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Parse errors - Invalid JSON response — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Validation errors - Invalid data format — a critical concept in cross-platform mobile development that you will use frequently in real projects
Error Recovery Patterns
- Retry with exponential backoff — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Fallback to cached data — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Show offline mode — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Graceful degradation — a critical concept in cross-platform mobile development that you will use frequently in real projects
- User-friendly error messages — a critical concept in cross-platform mobile development that you will use frequently in real projects
Error Handling Example
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, NetInfo } from '@react-native-community/netinfo';
// Error handling utility
class ApiError extends Error {
constructor(message, status, code) {
super(message);
this.name = 'ApiError';
this.status = status;
this.code = code;
}
}
// API service with error handling
const apiService = {
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 10000,
async request(url, options = {}) {
try {
// Check network connectivity
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
throw new ApiError('No internet connection', 0, 'NETWORK_ERROR');
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(`${this.baseURL}${url}`, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new ApiError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
'HTTP_ERROR'
);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'AbortError') {
throw new ApiError('Request timeout', 408, 'TIMEOUT_ERROR');
}
throw error;
}
},
async get(url) {
return this.request(url, { method: 'GET' });
},
async post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data),
});
},
};
// Error boundary component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<View style={styles.errorBoundary}>
<Text style={styles.errorBoundaryTitle}>Something went wrong</Text>
<Text style={styles.errorBoundaryText}>
{this.state.error?.message || 'Unknown error occurred'}
</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => this.setState({ hasError: false, error: null })}
>
<Text style={styles.retryButtonText}>Try Again</Text>
</TouchableOpacity>
</View>
);
}
return this.props.children;
}
}
// Error handling hook
const useErrorHandler = () => {
const [error, setError] = useState(null);
const [retryCount, setRetryCount] = useState(0);
const handleError = (error) => {
console.error('Error caught:', error);
setError(error);
};
const retry = () => {
setError(null);
setRetryCount(prev => prev + 1);
};
const reset = () => {
setError(null);
setRetryCount(0);
};
return { error, retryCount, handleError, retry, reset };
};
// Data fetching hook with error handling
const useApiData = (url, options = {}) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const { error, retryCount, handleError, retry, reset } = useErrorHandler();
const fetchData = async () => {
setLoading(true);
try {
const result = await apiService.get(url);
setData(result);
reset();
} catch (err) {
handleError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url, retryCount]);
return { data, loading, error, retry };
};
// Error display component
const ErrorDisplay = ({ error, onRetry }) => {
const getErrorMessage = (error) => {
if (error instanceof ApiError) {
switch (error.code) {
case 'NETWORK_ERROR':
return 'Please check your internet connection and try again.';
case 'TIMEOUT_ERROR':
return 'Request timed out. Please try again.';
case 'HTTP_ERROR':
return `Server error (${error.status}). Please try again later.`;
default:
return error.message;
}
}
return 'An unexpected error occurred. Please try again.';
};
const getErrorIcon = (error) => {
if (error instanceof ApiError) {
switch (error.code) {
case 'NETWORK_ERROR':
return '📡';
case 'TIMEOUT_ERROR':
return '⏰';
case 'HTTP_ERROR':
return '⚠️';
default:
return '❌';
}
}
return '❌';
};
return (
<View style={styles.errorContainer}>
<Text style={styles.errorIcon}>{getErrorIcon(error)}</Text>
<Text style={styles.errorTitle}>Oops! Something went wrong</Text>
<Text style={styles.errorMessage}>{getErrorMessage(error)}</Text>
<TouchableOpacity style={styles.retryButton} onPress={onRetry}>
<Text style={styles.retryButtonText}>Try Again</Text>
</TouchableOpacity>
</View>
);
};
// Main component
const ApiErrorHandling = () => {
const { data, loading, error, retry } = useApiData('/posts');
return (
<ErrorBoundary>
<View style={styles.container}>
<Text style={styles.title}>Error Handling Example</Text>
{loading && (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
{error && <ErrorDisplay error={error} onRetry={retry} />}
{data && !error && (
<View style={styles.dataContainer}>
<Text style={styles.dataTitle}>Posts Loaded Successfully!</Text>
<Text style={styles.dataCount}>Total posts: {data.length}</Text>
{data.slice(0, 3).map(post => (
<Text key={post.id} style={styles.dataItem}>
• {post.title}
</Text>
))}
</View>
)}
</View>
</ErrorBoundary>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 30,
color: '#333',
},
loadingContainer: {
alignItems: 'center',
padding: 20,
},
loadingText: {
fontSize: 16,
color: '#666',
},
errorBoundary: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
errorBoundaryTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#f44336',
marginBottom: 10,
},
errorBoundaryText: {
fontSize: 16,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
errorContainer: {
alignItems: 'center',
padding: 20,
backgroundColor: '#ffebee',
borderRadius: 8,
marginBottom: 20,
},
errorIcon: {
fontSize: 48,
marginBottom: 10,
},
errorTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#f44336',
marginBottom: 10,
},
errorMessage: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
retryButton: {
backgroundColor: '#2196F3',
padding: 15,
borderRadius: 8,
},
retryButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
dataContainer: {
backgroundColor: '#e8f5e8',
padding: 20,
borderRadius: 8,
},
dataTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#4CAF50',
marginBottom: 10,
},
dataCount: {
fontSize: 14,
color: '#666',
marginBottom: 10,
},
dataItem: {
fontSize: 14,
color: '#333',
marginBottom: 5,
},
});
export default ApiErrorHandling;