/** * React Query Context Provider * * This module sets up React Query with: * - Optimized default configurations * - Offline persistence support * - Environment-based cache duration settings * - Cross-platform compatibility (mobile + extension) */ import { onlineManager, QueryClient } from "@tanstack/react-query"; import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import React, { type ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { Platform } from "react-native"; import { CACHE_CONFIG } from "@/config/cache.config"; import { clearQueryCache, createQueryPersister } from "@/utils/queryPersister"; /** * Create and configure the QueryClient instance */ const createQueryClient = (): QueryClient => { return new QueryClient({ defaultOptions: { queries: { // How long data is considered fresh staleTime: CACHE_CONFIG.defaultStaleTime, // How long to keep unused data in cache gcTime: CACHE_CONFIG.gcTime, // Refetch behavior refetchOnWindowFocus: CACHE_CONFIG.refetch.onWindowFocus, refetchOnReconnect: CACHE_CONFIG.refetch.onReconnect, refetchOnMount: CACHE_CONFIG.refetch.onMount, // Retry configuration retry: CACHE_CONFIG.retry.count, retryDelay: CACHE_CONFIG.retry.delay, // Network mode - always try to fetch, use cache as fallback networkMode: "offlineFirst", }, mutations: { // Retry failed mutations retry: 1, retryDelay: 1000, // Network mode for mutations networkMode: "offlineFirst", }, }, }); }; /** * Props for the QueryProvider component */ interface QueryProviderProps { children: ReactNode; } /** * Context for exposing query utilities */ interface QueryContextValue { /** Invalidate all queries and refetch */ invalidateAllQueries: () => Promise; /** Clear the persisted cache */ clearCache: () => Promise; /** Check if the app is online */ isOnline: boolean; } const QueryContext = React.createContext(undefined); /** * QueryProvider component that wraps the app with React Query functionality * * Features: * - Automatic cache persistence to device storage * - Online/offline detection * - Configurable cache durations via environment variables */ export function QueryProvider({ children }: QueryProviderProps) { // Create QueryClient instance (stable reference) const [queryClient] = useState(() => createQueryClient()); // Create persister instance (stable reference) const [persister] = useState(() => createQueryPersister()); // Track online status const [isOnline, setIsOnline] = useState(true); // Setup online/offline detection useEffect(() => { // For web/extension if (Platform.OS === "web") { const handleOnline = () => { setIsOnline(true); onlineManager.setOnline(true); }; const handleOffline = () => { setIsOnline(false); onlineManager.setOnline(false); }; window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); // Set initial state setIsOnline(navigator.onLine); onlineManager.setOnline(navigator.onLine); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; } // For React Native, we could use NetInfo but it requires additional setup // For now, assume online on mobile (React Query handles network errors gracefully) return undefined; }, []); // Listen for reload messages from extension useEffect(() => { if (Platform.OS === "web") { const handleMessage = (event: MessageEvent) => { if (event.data?.type === "cal-companion-reload-cache") { queryClient.invalidateQueries(); } }; window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); } return undefined; }, [queryClient]); /** * Invalidate all queries and trigger refetch */ const invalidateAllQueries = useCallback(async () => { await queryClient.invalidateQueries(); }, [queryClient]); /** * Clear the persisted cache */ const clearCache = useCallback(async () => { queryClient.clear(); await clearQueryCache(); }, [queryClient]); const contextValue: QueryContextValue = useMemo( () => ({ invalidateAllQueries, clearCache, isOnline, }), [invalidateAllQueries, clearCache, isOnline] ); return ( { // Only persist successful queries return query.state.status === "success"; }, }, }} > {children} ); } /** * Hook to access query utilities */ export function useQueryContext(): QueryContextValue { const context = React.useContext(QueryContext); if (context === undefined) { throw new Error("useQueryContext must be used within a QueryProvider"); } return context; } /** * Export for direct QueryClient access when needed * (e.g., for prefetching outside of components) */ export { QueryClient };