import { useState, useEffect, useCallback, useRef } from 'react'; import { NativeEventEmitter, DeviceEventEmitter, AppState, Platform, NativeModules, type AppStateStatus, } from 'react-native'; import NativeNetworkState, { NetworkType } from './NativeNetworkState'; import type { NetworkState as NetworkStateType, NetworkDetails, NetworkCapabilities, } from './NativeNetworkState'; export interface UseNetworkStateOptions { /** * Whether to start listening automatically when the hook mounts * @default true */ autoStart?: boolean; } export interface UseNetworkStateReturn { /** * Current network state */ networkState: NetworkStateType | null; /** * Whether the hook is currently listening to network changes */ isListening: boolean; /** * Start listening to network state changes */ startListening: () => void; /** * Stop listening to network state changes */ stopListening: () => void; /** * Refresh network state manually */ refresh: () => Promise; /** * Check if specific network type is available */ isNetworkTypeAvailable: (type: NetworkType) => Promise; /** * Get network strength */ getNetworkStrength: () => Promise; /** * Check if network is expensive */ isNetworkExpensive: () => Promise; /** * Check if network is metered */ isNetworkMetered: () => Promise; /** * Check if connected to WiFi */ isConnectedToWifi: () => Promise; /** * Check if connected to cellular */ isConnectedToCellular: () => Promise; /** * Check if internet is reachable */ isInternetReachable: () => Promise; /** * Get WiFi details */ getWifiDetails: () => Promise; /** * Get network capabilities */ getNetworkCapabilities: () => Promise; } /** * React Hook for tracking network state * * @param options Configuration options * @returns Network state and utility functions * * @example * ```tsx * const { networkState, isListening, startListening, stopListening } = useNetworkState(); * * useEffect(() => { * if (networkState) { * console.log('Network type:', networkState.type); * console.log('Is connected:', networkState.isConnected); * } * }, [networkState]); * ``` */ export function useNetworkState( options: UseNetworkStateOptions = {} ): UseNetworkStateReturn { const { autoStart = true } = options; const [networkStateData, setNetworkStateData] = useState(null); const [isListening, setIsListening] = useState(false); const subscriptionRef = useRef<{ remove: () => void; } | null>(null); const isListeningRef = useRef(false); const refresh = useCallback(async () => { try { const state = await NativeNetworkState.getNetworkState(); setNetworkStateData(state); } catch (error) { console.error('Failed to refresh network state:', error); } }, []); const startListening = useCallback(() => { // Prevent multiple subscriptions using ref (avoids race condition with state updates) if (isListeningRef.current) return; // Clean up existing subscription if any (safety check) subscriptionRef.current?.remove?.(); subscriptionRef.current = null; isListeningRef.current = true; NativeNetworkState.startNetworkStateListener(); setIsListening(true); // Listen to network state changes const emitter = Platform.OS === 'ios' ? new NativeEventEmitter((NativeModules as any).NetworkState) : DeviceEventEmitter; const subscription = (emitter as any).addListener( 'networkStateChanged', (state: any) => { setNetworkStateData(state as NetworkStateType); } ); // Store subscription for cleanup in closure subscriptionRef.current = subscription as unknown as { remove: () => void; }; }, []); const stopListening = useCallback(() => { if (!isListeningRef.current) return; isListeningRef.current = false; NativeNetworkState.stopNetworkStateListener(); setIsListening(false); // Remove subscription subscriptionRef.current?.remove?.(); subscriptionRef.current = null; }, []); // Handle app state changes (background/foreground) useEffect(() => { const handleAppStateChange = (nextAppState: AppStateStatus) => { // Use ref instead of state to avoid race condition with batched async updates if (nextAppState === 'active' && isListeningRef.current) { // App came to foreground, force refresh network state NativeNetworkState.forceRefresh(); // Also refresh local state refresh(); } }; const subscription = AppState.addEventListener( 'change', handleAppStateChange ); return () => { subscription?.remove(); }; }, [refresh]); useEffect(() => { if (autoStart) { startListening(); } // Initial network state refresh(); return () => { if (isListeningRef.current) { stopListening(); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [autoStart]); return { networkState: networkStateData, isListening, startListening, stopListening, refresh, isNetworkTypeAvailable: (type: NetworkType) => NativeNetworkState.isNetworkTypeAvailable(type), getNetworkStrength: () => NativeNetworkState.getNetworkStrength(), isNetworkExpensive: () => NativeNetworkState.isNetworkExpensive(), isNetworkMetered: () => NativeNetworkState.isNetworkMetered(), isConnectedToWifi: async () => { const state = await NativeNetworkState.getNetworkState(); return state.type === NetworkType.WIFI && state.isConnected; }, isConnectedToCellular: async () => { const state = await NativeNetworkState.getNetworkState(); return state.type === NetworkType.CELLULAR && state.isConnected; }, isInternetReachable: async () => { const state = await NativeNetworkState.getNetworkState(); return state.isInternetReachable; }, getWifiDetails: async () => { const state = await NativeNetworkState.getNetworkState(); if (state.type === NetworkType.WIFI && state.details) { return state.details; } return null; }, getNetworkCapabilities: async () => { const state = await NativeNetworkState.getNetworkState(); return state.details?.capabilities || null; }, }; } export default useNetworkState;