import { useState, useEffect, useCallback, useRef } from "react"; /** * Hook to detect if the user is online/offline */ export function useOnlineStatus(): boolean { const [isOnline, setIsOnline] = useState( typeof navigator !== "undefined" ? navigator.onLine : true ); useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []); return isOnline; } /** * Hook to debounce a value */ export function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } /** * Hook to track previous value */ export function usePrevious(value: T): T | undefined { const ref = useRef(undefined); useEffect(() => { ref.current = value; }, [value]); return ref.current; } /** * Hook for async operations with loading and error states */ interface UseAsyncOptions { onSuccess?: (data: T) => void; onError?: (error: Error) => void; } export function useAsync( asyncFunction: (...args: Args) => Promise, options: UseAsyncOptions = {} ) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const execute = useCallback( async (...args: Args) => { setIsLoading(true); setError(null); try { const result = await asyncFunction(...args); setData(result); options.onSuccess?.(result); return result; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); setError(error); options.onError?.(error); throw error; } finally { setIsLoading(false); } }, [asyncFunction, options] ); return { data, isLoading, error, execute, setData }; } /** * Hook for local storage with sync across tabs */ export function useLocalStorage( key: string, initialValue: T ): [T, (value: T | ((val: T) => T)) => void] { const [storedValue, setStoredValue] = useState(() => { if (typeof window === "undefined") { return initialValue; } try { const item = window.localStorage.getItem(key); return item ? (JSON.parse(item) as T) : initialValue; } catch (error) { console.error(`Error reading localStorage key "${key}":`, error); return initialValue; } }); const setValue = useCallback( (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); if (typeof window !== "undefined") { window.localStorage.setItem(key, JSON.stringify(valueToStore)); } } catch (error) { console.error(`Error setting localStorage key "${key}":`, error); } }, [key, storedValue] ); // Listen for changes from other tabs useEffect(() => { const handleStorageChange = (event: StorageEvent) => { if (event.key === key && event.newValue) { try { setStoredValue(JSON.parse(event.newValue) as T); } catch (error) { console.error(`Error parsing localStorage change for key "${key}":`, error); } } }; window.addEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange); }, [key]); return [storedValue, setValue]; } /** * Hook to detect if user has reduced motion preference */ export function usePrefersReducedMotion(): boolean { const [prefersReducedMotion, setPrefersReducedMotion] = useState(false); useEffect(() => { const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); setPrefersReducedMotion(mediaQuery.matches); const handler = (event: MediaQueryListEvent) => { setPrefersReducedMotion(event.matches); }; mediaQuery.addEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler); }, []); return prefersReducedMotion; } /** * Hook to track page visibility */ export function usePageVisibility(): boolean { const [isVisible, setIsVisible] = useState(true); useEffect(() => { const handleVisibilityChange = () => { setIsVisible(!document.hidden); }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, []); return isVisible; }