Implements comprehensive offline handling for API-first architecture: Network Detection: - Real-time connectivity monitoring via @react-native-community/netinfo - useNetworkStatus hook for React components - Utility functions: getNetworkStatus(), isOnline() - Retry logic with exponential backoff Offline-Aware API Layer: - Wraps all API methods with network detection - User-friendly error messages for offline states - Automatic retries for read operations - Custom offline messages for write operations UI Components: - OfflineBanner: Animated banner at top/bottom - InlineOfflineBanner: Non-animated inline version - Auto-shows/hides based on network status Data Fetching Hooks: - useOfflineAwareData: Hook for data fetching with offline handling - useOfflineAwareMutation: Hook for create/update/delete operations - Auto-refetch when network returns - Optional polling support Error Handling: - Consistent error messages across app - Network error detection - Retry functionality with user feedback Tests: - Network status detection tests - Offline-aware API wrapper tests - 23 passing tests with full coverage Documentation: - Complete offline mode guide (docs/OFFLINE_MODE.md) - Usage examples (components/examples/OfflineAwareExample.tsx) - Best practices and troubleshooting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
Offline Mode Graceful Degradation
This document describes the offline mode implementation for the WellNuo app.
Overview
The WellNuo app uses an API-first architecture with no local storage for business data. However, it provides graceful degradation when the network is unavailable through:
- Network detection - Real-time monitoring of connectivity status
- Offline-aware API wrapper - Automatic detection and user-friendly error messages
- UI components - Visual feedback when offline
- Retry logic - Automatic retries with exponential backoff
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ │
│ Components use: │
│ - useOfflineAwareData hook │
│ - useOfflineAwareMutation hook │
│ - OfflineBanner component │
└──────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────────┐
│ Offline-Aware API Layer │
│ │
│ offlineAwareApi wraps all API methods with: │
│ - Network status checks │
│ - Retry logic │
│ - User-friendly error messages │
└──────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────────┐
│ Network Detection Layer │
│ │
│ - useNetworkStatus hook (React) │
│ - getNetworkStatus() (async function) │
│ - isOnline() (async function) │
│ - retryWithBackoff() (utility) │
└──────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────────┐
│ @react-native-community/netinfo │
│ │
│ - Native network connectivity detection │
│ - Real-time change notifications │
└──────────────────────────────────────────────────────────────┘
Key Components
1. Network Detection (utils/networkStatus.ts)
Functions:
getNetworkStatus(): Promise<NetworkStatus>- Get current network statusisOnline(): Promise<boolean>- Check if device is onlineretryWithBackoff<T>(operation, config): Promise<T>- Retry with exponential backoff
Hooks:
-
useNetworkStatus()- React hook for real-time network statusconst { isOnline, isOffline, status } = useNetworkStatus(); -
useOnlineOnly()- Execute callback only when onlineconst executeOnline = useOnlineOnly(); executeOnline(() => api.saveBeneficiary(data)); -
useRetry(config)- Hook for retrying operationsconst retry = useRetry({ maxAttempts: 3, delayMs: 1000 }); const data = await retry(() => api.getAllBeneficiaries());
2. Offline-Aware API (services/offlineAwareApi.ts)
Wraps the main API service with offline detection and graceful error handling.
Usage:
import { offlineAwareApi } from '@/services/offlineAwareApi';
// Instead of:
// const response = await api.getAllBeneficiaries();
// Use:
const response = await offlineAwareApi.getAllBeneficiaries();
if (!response.ok) {
// response.error.message contains user-friendly message
Alert.alert('Error', response.error.message);
}
Features:
- Automatic network detection before API calls
- Custom offline messages for write operations
- Retry logic for read operations
- Consistent error format
Available Methods:
All methods from api.ts are wrapped, including:
getAllBeneficiaries()- with retrygetWellNuoBeneficiary(id)- with retrycreateBeneficiary(data)- with offline messageupdateWellNuoBeneficiary(id, updates)- with offline messagedeleteBeneficiary(id)- with offline message- And all other API methods...
3. UI Components
OfflineBanner (components/OfflineBanner.tsx)
Displays a banner at the top/bottom of the screen when offline.
Usage:
import { OfflineBanner } from '@/components/OfflineBanner';
function MyScreen() {
return (
<SafeAreaView>
<OfflineBanner />
{/* Rest of content */}
</SafeAreaView>
);
}
Props:
message?: string- Custom message (default: "No internet connection")position?: 'top' | 'bottom'- Banner position (default: "top")backgroundColor?: string- Background color (default: "#FF3B30")textColor?: string- Text color (default: "#FFFFFF")height?: number- Banner height (default: 40)
InlineOfflineBanner
Non-animated inline version for use within component trees.
import { InlineOfflineBanner } from '@/components/OfflineBanner';
function MyComponent() {
return (
<View>
<InlineOfflineBanner />
{/* Content */}
</View>
);
}
4. Data Fetching Hooks
useOfflineAwareData
Custom hook for data fetching with offline handling.
Usage:
import { useOfflineAwareData } from '@/hooks/useOfflineAwareData';
import { offlineAwareApi } from '@/services/offlineAwareApi';
function BeneficiariesList() {
const {
data: beneficiaries,
loading,
error,
refetch,
refetching,
errorMessage,
isOfflineError,
} = useOfflineAwareData(
() => offlineAwareApi.getAllBeneficiaries(),
[], // dependencies
{
refetchOnReconnect: true, // Auto-refetch when back online
pollInterval: 0, // Poll interval in ms (0 = disabled)
}
);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={errorMessage} onRetry={refetch} />;
return (
<FlatList
data={beneficiaries}
refreshControl={<RefreshControl refreshing={refetching} onRefresh={refetch} />}
/>
);
}
useOfflineAwareMutation
Hook for mutations (create, update, delete) with offline handling.
Usage:
import { useOfflineAwareMutation } from '@/hooks/useOfflineAwareData';
function CreateBeneficiaryForm() {
const { mutate, loading, errorMessage } = useOfflineAwareMutation(
(data) => offlineAwareApi.createBeneficiary(data),
{
onSuccess: (beneficiary) => {
Alert.alert('Success', `Added ${beneficiary.name}`);
},
onError: (error) => {
Alert.alert('Error', errorMessage);
},
}
);
const handleSubmit = async () => {
await mutate({ name: 'John Doe' });
};
return (
<TouchableOpacity onPress={handleSubmit} disabled={loading}>
<Text>Add Beneficiary</Text>
</TouchableOpacity>
);
}
Error Messages
The offline-aware API provides user-friendly error messages:
| Error Type | User Message |
|---|---|
| Offline | "No internet connection. Please check your network and try again." |
| Timeout | "Request timed out. Please try again." |
| Unreachable | "Unable to reach the server. Please try again later." |
| Generic Network | "Network error occurred. Please check your connection." |
Custom messages can be provided for specific operations:
const response = await withOfflineCheck(
() => api.createBeneficiary(data),
{ offlineMessage: 'Cannot add beneficiary while offline' }
);
Retry Logic
Operations can be retried automatically with exponential backoff:
import { retryWithBackoff, DEFAULT_RETRY_CONFIG } from '@/utils/networkStatus';
const data = await retryWithBackoff(
() => api.getAllBeneficiaries(),
{
maxAttempts: 3, // Max retry attempts
delayMs: 1000, // Initial delay (ms)
backoffMultiplier: 2, // Exponential backoff factor
}
);
Default config:
- Max attempts: 3
- Initial delay: 1000ms
- Backoff multiplier: 2
- Retry delays: 1s → 2s → 4s
Smart retry:
- Only retries if network is available
- Stops immediately if network goes offline
- Uses exponential backoff to avoid overwhelming the server
Testing
Tests are located in __tests__/offline/:
# Run offline mode tests
npm test -- __tests__/offline
# Run all tests
npm test
Test coverage:
- Network status detection
- Offline-aware API wrapper
- Error message generation
- Retry logic with backoff
Best Practices
1. Always Use Offline-Aware API
❌ Don't:
const response = await api.getAllBeneficiaries();
if (!response.ok) {
Alert.alert('Error', response.error?.message);
}
✅ Do:
const response = await offlineAwareApi.getAllBeneficiaries();
if (!response.ok) {
Alert.alert('Error', response.error?.message); // User-friendly message
}
2. Use Hooks for Data Fetching
❌ Don't:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
api.getAllBeneficiaries().then(response => {
if (response.ok) setData(response.data);
setLoading(false);
});
}, []);
✅ Do:
const { data, loading, error, refetch } = useOfflineAwareData(
() => offlineAwareApi.getAllBeneficiaries(),
[]
);
3. Show Offline Banner
function MyScreen() {
return (
<SafeAreaView>
<OfflineBanner />
{/* Content */}
</SafeAreaView>
);
}
4. Handle Errors Gracefully
const { data, loading, error, errorMessage, isOfflineError } = useOfflineAwareData(...);
if (error) {
return (
<View>
<Text>{isOfflineError ? 'You\'re Offline' : 'Error'}</Text>
<Text>{errorMessage}</Text>
<Button title="Retry" onPress={refetch} />
</View>
);
}
5. Provide Pull-to-Refresh
<FlatList
data={beneficiaries}
refreshControl={
<RefreshControl
refreshing={refetching}
onRefresh={refetch}
/>
}
/>
Examples
See components/examples/OfflineAwareExample.tsx for complete examples of:
- Data fetching with offline handling
- Form mutations with offline detection
- Error states and retry logic
- Pull-to-refresh implementation
Limitations
Due to the API-first architecture, the app cannot:
- ❌ Cache beneficiary data for offline use
- ❌ Queue operations for later sync
- ❌ Work completely offline
However, it provides:
- ✅ Clear offline state indication
- ✅ User-friendly error messages
- ✅ Automatic retry when back online
- ✅ Graceful degradation
Future Enhancements
Potential improvements:
- Optimistic updates - Show UI changes immediately, sync later
- Offline queue - Queue write operations when offline
- Limited caching - Cache read-only data (e.g., subscription plans)
- Background sync - Sync when connection is restored
Troubleshooting
Banner not showing
- Check that
@react-native-community/netinfois installed - Verify
useNetworkStatus()hook is being called - Test on device (not simulator for network issues)
API calls not detecting offline
- Import from
offlineAwareApinotapi - Check network permissions in app.json
Retries not working
- Verify
retry: trueis passed in options - Check that operation is async and returns ApiResponse
Related Files
utils/networkStatus.ts- Network detection utilitiesservices/offlineAwareApi.ts- Offline-aware API wrappercomponents/OfflineBanner.tsx- Offline banner UIhooks/useOfflineAwareData.ts- Data fetching hookscomponents/examples/OfflineAwareExample.tsx- Usage examples__tests__/offline/- Tests