/** * 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', }, });