diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..7a35919 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,94 @@ +import { useState, useEffect, useCallback } from 'react'; +import { handleError } from '../utils/errorHandling'; + +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]; +}