import { useState, useEffect, useCallback, Dispatch, SetStateAction } from "react"; type SetValue = Dispatch>; export function useLocalStorage( key: string, initialValue: T ): [T, SetValue] { // Get initial value from localStorage or use the provided initial value const readValue = useCallback((): T => { // Prevent build error "window is undefined" during SSR if (typeof window === "undefined") { return initialValue; } try { const item = window.localStorage.getItem(key); return item ? (JSON.parse(item) as T) : initialValue; } catch (error) { console.warn(`Error reading localStorage key "${key}":`, error); return initialValue; } }, [initialValue, key]); // State to store our value const [storedValue, setStoredValue] = useState(readValue); // Return a wrapped version of useState's setter function that // persists the new value to localStorage const setValue: SetValue = useCallback( (value) => { // Prevent build error "window is undefined" during SSR if (typeof window === "undefined") { console.warn( `Tried setting localStorage key "${key}" even though environment is not a client` ); } try { // Allow value to be a function so we have the same API as useState const newValue = value instanceof Function ? value(storedValue) : value; // Save to local storage window.localStorage.setItem(key, JSON.stringify(newValue)); // Save state setStoredValue(newValue); // Dispatch a custom event so every useLocalStorage hook is notified window.dispatchEvent(new Event("local-storage")); } catch (error) { console.warn(`Error setting localStorage key "${key}":`, error); } }, [key, storedValue] ); // Listen for changes to this key from other tabs/windows useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === key && e.newValue !== null) { try { setStoredValue(JSON.parse(e.newValue)); } catch (error) { console.warn( `Error parsing localStorage value for key "${key}":`, error ); } } }; // Listen for storage events (changes from other tabs) window.addEventListener("storage", handleStorageChange); // Listen for custom event (changes from same tab) const handleLocalStorageChange = () => { setStoredValue(readValue()); }; window.addEventListener("local-storage", handleLocalStorageChange); return () => { window.removeEventListener("storage", handleStorageChange); window.removeEventListener( "local-storage", handleLocalStorageChange ); }; }, [key, readValue]); return [storedValue, setValue]; }