"use client"; import * as React from "react"; /** * Persists state to localStorage with SSR safety and JSON serialization. * * @remarks * This hook synchronizes state with localStorage, allowing data to persist * across page refreshes and browser sessions. It is SSR-safe and returns the * initial value on the server until hydration completes. The hook also syncs * state across tabs/windows via the `storage` event. * * Error handling: * - On initial load, a JSON parse failure falls back to `initialValue`. * - On a cross-tab `storage` event with malformed JSON, the event is ignored * and the current local state is preserved (to avoid wiping user state on a * bad cross-tab message). * - `localStorage` write failures (quota exceeded, private-mode restrictions) * are swallowed silently — the in-memory state update still succeeds. * * @typeParam T - The type of the value being stored. * @param key - The localStorage key to store the value under. * @param initialValue - The default value to use if no value is found in localStorage. * @returns A tuple containing the current value and a setter function. * * @example * ```tsx * function UserSettings() { * const [theme, setTheme] = useLocalStorage("theme", "light"); * * return ( * * ); * } * ``` * * @example * ```tsx * function ShoppingCart() { * const [cart, setCart] = useLocalStorage("cart", []); * * return ( * * ); * } * ``` */ export function useLocalStorage(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] { const [storedValue, setStoredValue] = React.useState(() => { // SSR safety: return initial value on server if (globalThis.window === undefined) { return initialValue; } try { const item = globalThis.window.localStorage.getItem(key); return item === null ? initialValue : (JSON.parse(item) as T); } catch { return initialValue; } }); const setValue = React.useCallback( (value: T | ((prev: T) => T)) => { try { setStoredValue((currentValue) => { const valueToStore = typeof value === "function" ? (value as (prev: T) => T)(currentValue) : value; if (globalThis.window !== undefined) { globalThis.window.localStorage.setItem(key, JSON.stringify(valueToStore)); } return valueToStore; }); } catch { // localStorage writes can throw (quota exceeded, private mode, etc.). Fail silently. } }, [key], ); React.useEffect(() => { // SSR safety: window is not available on server if (globalThis.window === undefined) { return; } const handleStorageChange = (event: StorageEvent) => { if (event.key !== key || event.storageArea !== globalThis.window.localStorage) { return; } try { const newValue = event.newValue === null ? initialValue : (JSON.parse(event.newValue) as T); setStoredValue(newValue); } catch { // Parse failures on cross-tab storage events fall through silently. } }; globalThis.window.addEventListener("storage", handleStorageChange); return () => { globalThis.window.removeEventListener("storage", handleStorageChange); }; }, [key, initialValue]); return [storedValue, setValue]; }