/** * MemoryStack Provider Component * Wrap your app with this to enable MemoryStack hooks */ import React, { useEffect, useState, useCallback, useMemo, ReactNode } from 'react'; import { MemoryStackClient } from '../client'; import { MemoryStackContext, MemoryStackContextValue } from './MemoryStackContext'; import type { MemoryStackConfig } from '../types'; export interface MemoryStackProviderProps { /** Children components */ children: ReactNode; /** MemoryStack configuration */ config: MemoryStackConfig; /** * Optional AsyncStorage instance for offline support * Pass @react-native-async-storage/async-storage */ storage?: { getItem: (key: string) => Promise; setItem: (key: string, value: string) => Promise; removeItem: (key: string) => Promise; }; /** * Optional NetInfo instance for network detection * Pass @react-native-community/netinfo */ netInfo?: { fetch: () => Promise<{ isConnected: boolean | null; isInternetReachable: boolean | null }>; addEventListener: (callback: (state: { isConnected: boolean | null }) => void) => () => void; }; } /** * MemoryStack Provider * * @example * ```tsx * import AsyncStorage from '@react-native-async-storage/async-storage'; * import NetInfo from '@react-native-community/netinfo'; * * function App() { * return ( * * * * ); * } * ``` */ export function MemoryStackProvider({ children, config, storage, netInfo, }: MemoryStackProviderProps): React.ReactElement { const [client, setClient] = useState(null); const [isOnline, setIsOnline] = useState(true); const [pendingOperations, setPendingOperations] = useState(0); const [isInitialized, setIsInitialized] = useState(false); // Initialize client useEffect(() => { const newClient = new MemoryStackClient(config); // Set up storage if provided if (storage) { newClient.setStorage(storage); } // Set up network provider if provided if (netInfo) { newClient.setNetworkProvider({ isConnected: async () => { const state = await netInfo.fetch(); return state.isConnected ?? true; }, addEventListener: (callback) => { return netInfo.addEventListener((state) => { callback(state.isConnected ?? true); }); }, }); // Get initial network state netInfo.fetch().then((state) => { setIsOnline(state.isConnected ?? true); }); // Subscribe to network changes const unsubscribe = netInfo.addEventListener((state) => { setIsOnline(state.isConnected ?? true); }); setClient(newClient); setIsInitialized(true); return () => { unsubscribe(); }; } setClient(newClient); setIsInitialized(true); return undefined; }, [config.apiKey, config.baseUrl]); // Update pending operations count useEffect(() => { if (!client || !config.enableOffline) return; const updatePendingCount = async () => { try { const status = await client.getPendingOperations(); setPendingOperations(status.pendingCount); } catch { // Ignore errors } }; // Check immediately updatePendingCount(); // Poll every 5 seconds const interval = setInterval(updatePendingCount, 5000); return () => clearInterval(interval); }, [client, config.enableOffline]); // Sync pending operations const syncPending = useCallback(async () => { if (!client) return; try { const result = await client.processPendingOperations(); setPendingOperations(result.remaining); } catch (error) { console.error('[MemoryStack] Failed to sync pending operations:', error); } }, [client]); // Memoize context value const contextValue = useMemo( () => ({ client, isOnline, pendingOperations, syncPending, isInitialized, }), [client, isOnline, pendingOperations, syncPending, isInitialized] ); return ( {children} ); } export default MemoryStackProvider;