/**
* Example: Offline-Aware Component
*
* Demonstrates best practices for implementing offline mode graceful degradation.
* This example shows how to:
* 1. Use useOfflineAwareData hook for data fetching
* 2. Display offline banners
* 3. Handle loading and error states
* 4. Provide retry functionality
*
* NOTE: This is a reference implementation. Copy patterns from this file
* when adding offline handling to actual screens.
*/
import React from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { OfflineBanner } from '@/components/OfflineBanner';
import { useOfflineAwareData } from '@/hooks/useOfflineAwareData';
import { offlineAwareApi } from '@/services/offlineAwareApi';
import { AppColors, Spacing, FontSizes } from '@/constants/theme';
import type { Beneficiary } from '@/types';
/**
* Example: Beneficiaries List with Offline Handling
*/
export function BeneficiariesListExample() {
// Use offline-aware data hook
const {
data: beneficiaries,
loading,
error,
refetch,
refetching,
errorMessage,
isOfflineError,
} = useOfflineAwareData(
() => offlineAwareApi.getAllBeneficiaries(),
[], // Dependencies
{
refetchOnReconnect: true, // Auto-refetch when back online
pollInterval: 0, // Disable polling (set to e.g., 30000 for 30s polling)
}
);
// Loading state (initial load)
if (loading) {
return (
Loading beneficiaries...
);
}
// Error state (with retry button)
if (error && !beneficiaries) {
return (
{isOfflineError ? 'You\'re Offline' : 'Something Went Wrong'}
{errorMessage}
Try Again
);
}
// Empty state
if (!beneficiaries || beneficiaries.length === 0) {
return (
No Beneficiaries
Add someone to start monitoring
);
}
// Success state - show data
return (
{/* Offline banner - auto-shows when offline */}
{/* List with pull-to-refresh */}
item.id.toString()}
renderItem={({ item }) => (
{item.displayName}
{item.status}
)}
refreshControl={
}
contentContainerStyle={styles.listContent}
/>
{/* Show error banner if refetch fails (but we still have cached data) */}
{error && (
{errorMessage}
Retry
)}
);
}
/**
* Example: Form with Offline Mutation
*/
import { useOfflineAwareMutation } from '@/hooks/useOfflineAwareData';
import { Alert } from 'react-native';
export function CreateBeneficiaryExample() {
const [name, setName] = React.useState('');
const { mutate, loading, errorMessage, isOfflineError } = useOfflineAwareMutation(
(data: { name: string }) => offlineAwareApi.createBeneficiary(data),
{
offlineMessage: 'Cannot create beneficiary while offline',
onSuccess: (beneficiary) => {
Alert.alert('Success', `Added ${beneficiary.name}`);
setName('');
},
onError: (error) => {
Alert.alert('Error', errorMessage || 'Failed to add beneficiary');
},
}
);
const handleSubmit = async () => {
if (!name.trim()) {
Alert.alert('Error', 'Please enter a name');
return;
}
await mutate({ name: name.trim() });
};
return (
Beneficiary Name
{/* Add TextInput here */}
{errorMessage && (
{errorMessage}
)}
{loading ? (
) : (
Add Beneficiary
)}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.background,
},
centerContent: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: Spacing.lg,
},
loadingText: {
marginTop: Spacing.md,
fontSize: FontSizes.md,
color: AppColors.textSecondary,
},
errorTitle: {
fontSize: FontSizes.xl,
fontWeight: '600',
color: AppColors.textPrimary,
marginBottom: Spacing.sm,
},
errorMessage: {
fontSize: FontSizes.md,
color: AppColors.textSecondary,
textAlign: 'center',
marginBottom: Spacing.lg,
},
retryButton: {
backgroundColor: AppColors.primary,
paddingHorizontal: Spacing.lg,
paddingVertical: Spacing.md,
borderRadius: 8,
},
retryButtonText: {
color: '#fff',
fontSize: FontSizes.md,
fontWeight: '600',
},
emptyTitle: {
fontSize: FontSizes.xl,
fontWeight: '600',
color: AppColors.textPrimary,
marginBottom: Spacing.sm,
},
emptyMessage: {
fontSize: FontSizes.md,
color: AppColors.textSecondary,
},
listContent: {
padding: Spacing.md,
},
listItem: {
backgroundColor: AppColors.backgroundSecondary,
padding: Spacing.md,
borderRadius: 8,
marginBottom: Spacing.sm,
flexDirection: 'row',
justifyContent: 'space-between',
},
itemName: {
fontSize: FontSizes.md,
fontWeight: '600',
color: AppColors.textPrimary,
},
itemStatus: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
},
errorBanner: {
backgroundColor: AppColors.danger,
padding: Spacing.md,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
errorBannerText: {
flex: 1,
color: '#fff',
fontSize: FontSizes.sm,
},
errorBannerRetry: {
color: '#fff',
fontSize: FontSizes.sm,
fontWeight: '600',
},
formContainer: {
padding: Spacing.lg,
},
formLabel: {
fontSize: FontSizes.md,
fontWeight: '600',
color: AppColors.textPrimary,
marginBottom: Spacing.sm,
},
formError: {
fontSize: FontSizes.sm,
color: AppColors.danger,
marginTop: Spacing.sm,
},
submitButton: {
backgroundColor: AppColors.primary,
padding: Spacing.md,
borderRadius: 8,
alignItems: 'center',
marginTop: Spacing.lg,
},
submitButtonDisabled: {
opacity: 0.6,
},
submitButtonText: {
color: '#fff',
fontSize: FontSizes.md,
fontWeight: '600',
},
});