All files / src/hooks useLocalStorage.ts

90% Statements 27/30
78.57% Branches 11/14
100% Functions 7/7
90% Lines 27/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                                                                                                    116x 62x       62x 62x 62x   3x 3x       116x 33x 33x 33x   33x 33x 33x   1x       33x             116x 62x       62x 3x 2x 2x   1x         62x   62x 62x       116x    
import { useState, useEffect, useCallback } from 'react';
 
/**
 * Custom hook to sync state with localStorage
 * 
 * ## Business Perspective
 * 
 * Enables widgets to remember user preferences and settings across browser
 * sessions, improving user experience by maintaining personalized configurations.
 * This is particularly valuable for security officers who configure specific
 * security levels and want their settings persisted. 💾
 * 
 * ## Technical Perspective
 * 
 * Provides a React state hook that automatically syncs with localStorage,
 * handling JSON serialization/deserialization and SSR compatibility. Uses
 * the same API as useState for familiarity.
 * 
 * @template T - Type of the stored value
 * @param key - localStorage key to use for storage
 * @param initialValue - Initial value if key doesn't exist in localStorage
 * @returns Tuple of [storedValue, setValue] similar to useState
 * 
 * @example
 * ```tsx
 * // Store user's preferred theme
 * const [theme, setTheme] = useLocalStorage('theme', 'light');
 * 
 * return (
 *   <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
 *     Toggle Theme
 *   </button>
 * );
 * ```
 * 
 * @example
 * ```tsx
 * // Store security level preferences
 * const [savedLevels, setSavedLevels] = useLocalStorage('securityLevels', {
 *   availability: 'Moderate',
 *   integrity: 'Moderate',
 *   confidentiality: 'Moderate'
 * });
 * ```
 * 
 * @example
 * ```tsx
 * // Store widget visibility preferences
 * const [widgetPrefs, setWidgetPrefs] = useLocalStorage('widgetPreferences', {
 *   showDetails: true,
 *   expandedSections: ['overview', 'metrics']
 * });
 * ```
 */
export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    Iif (typeof window === 'undefined') {
      return initialValue;
    }
    
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });
 
  const setValue = useCallback((value: T | ((prev: T) => T)) => {
    try {
      setStoredValue((prevValue) => {
        const valueToStore = value instanceof Function ? value(prevValue) : value;
        
        Eif (typeof window !== 'undefined') {
          try {
            window.localStorage.setItem(key, JSON.stringify(valueToStore));
          } catch (storageError) {
            console.error(`Error saving to localStorage key "${key}":`, storageError);
          }
        }
        
        return valueToStore;
      });
    } catch (error) {
      console.error(`Error saving to localStorage key "${key}":`, error);
    }
  }, [key]);
 
  useEffect(() => {
    Iif (typeof window === 'undefined') {
      return;
    }
 
    const handleStorageChange = (e: StorageEvent): void => {
      if (e.key === key && e.newValue !== null) {
        try {
          setStoredValue(JSON.parse(e.newValue));
        } catch (error) {
          console.warn(`Error parsing storage event for key "${key}":`, error);
        }
      }
    };
 
    window.addEventListener('storage', handleStorageChange);
 
    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  }, [key]);
 
  return [storedValue, setValue];
}