import { useState, useEffect, useCallback } from 'react'; import { handleError } from '../utils/errorHandling'; /** * Function type for updating the stored value * @template T - The type of the stored value * @param value - Either a new value of type T or an updater function that takes the current value and returns a new value */ type SetValue = (value: T | ((val: T) => T)) => void; /** * Custom hook for managing state in localStorage with automatic serialization * * @template T - The type of the stored value * @param {string} key - The localStorage key to use * @param {T | (() => T)} initialValue - The initial value or a function that returns the initial value * @returns {[T, SetValue, () => void]} A tuple containing: * - The current stored value * - A function to update the stored value * - A function to remove the value from storage * * @example * ```tsx * const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light'); * setTheme('dark'); // Updates both state and localStorage * removeTheme(); // Removes from localStorage and resets to initial value * ``` * * @remarks * - SSR-safe: Returns initial value during server-side rendering * - Automatically serializes/deserializes JSON * - Handles localStorage quota exceeded errors * - Handles invalid JSON gracefully */ export function useLocalStorage( key: string, initialValue: T | (() => T) ): [T, SetValue, () => void] { // Get initial value - SSR safe const getInitialValue = useCallback((): T => { // Check if we're in a browser environment if (typeof window === 'undefined') { return initialValue instanceof Function ? initialValue() : initialValue; } try { const item = window.localStorage.getItem(key); if (item) { return JSON.parse(item) as T; } } catch (error) { handleError(error, `LocalStorage Read (key: ${key})`); } return initialValue instanceof Function ? initialValue() : initialValue; }, [key, initialValue]); const [storedValue, setStoredValue] = useState(getInitialValue); // Update localStorage whenever storedValue changes useEffect(() => { if (typeof window === 'undefined') { return; } try { window.localStorage.setItem(key, JSON.stringify(storedValue)); } catch (error) { handleError(error, `LocalStorage Write (key: ${key})`); } }, [key, storedValue]); // Set value function that supports both direct values and updater functions const setValue: SetValue = useCallback((value) => { try { setStoredValue((prevValue) => { const newValue = value instanceof Function ? value(prevValue) : value; return newValue; }); } catch (error) { handleError(error, `LocalStorage Update (key: ${key})`); } }, [key]); // Remove value from localStorage and reset to initial value const removeValue = useCallback(() => { try { if (typeof window !== 'undefined') { window.localStorage.removeItem(key); } const resetValue = initialValue instanceof Function ? initialValue() : initialValue; setStoredValue(resetValue); } catch (error) { handleError(error, `LocalStorage Remove (key: ${key})`); } }, [key, initialValue]); return [storedValue, setValue, removeValue]; }