useLocalStorage
Persist state in localStorage with automatic synchronization
A React hook that syncs state with localStorage and supports cross-tab synchronization.
Usage
import { useLocalStorage } from "@your-org/react-utils";function UserPreferences() {const [theme, setTheme, removeTheme] = useLocalStorage("theme", "light");return (<div><p>Current theme: {theme}</p><button onClick={() => setTheme("dark")}>Dark Mode</button><button onClick={() => setTheme("light")}>Light Mode</button><button onClick={removeTheme}>Reset</button></div>);}
Features
- SSR-safe (returns initial value on server)
- Cross-tab synchronization
- Type-safe with TypeScript generics
- Automatic JSON serialization/deserialization
- Error handling with fallbacks
- Optional remove function
API Reference
Parameters
key(string) - The localStorage keyinitialValue(T) - Default value if key doesn't exist
Returns
Returns a tuple with:
storedValue(T) - Current value from localStoragesetValue(function) - Update the value (supports updater function)removeValue(function) - Remove the key from localStorage
Implementation
import { useState, useEffect, useCallback } from "react";type SetValue<T> = T | ((val: T) => T);export function useLocalStorage<T>(key: string,initialValue: T): [T, (value: SetValue<T>) => void, () => void] {// Get initial value from localStorage or use initialValueconst [storedValue, setStoredValue] = useState<T>(() => {if (typeof window === "undefined") {return initialValue;}try {const item = window.localStorage.getItem(key);return item ? JSON.parse(item) : initialValue;} catch (error) {console.warn(`Error loading localStorage key "${key}":`, error);return initialValue;}});// Update localStorage when value changesconst setValue = useCallback((value: SetValue<T>) => {try {const valueToStore =value instanceof Function ? value(storedValue) : value;setStoredValue(valueToStore);if (typeof window !== "undefined") {window.localStorage.setItem(key, JSON.stringify(valueToStore));// Dispatch custom event for cross-tab syncwindow.dispatchEvent(new CustomEvent("local-storage", {detail: { key, value: valueToStore },}));}} catch (error) {console.warn(`Error setting localStorage key "${key}":`, error);}},[key, storedValue]);// Remove item from localStorageconst removeValue = useCallback(() => {try {if (typeof window !== "undefined") {window.localStorage.removeItem(key);setStoredValue(initialValue);}} catch (error) {console.warn(`Error removing localStorage key "${key}":`, error);}}, [key, initialValue]);// Listen for changes in other tabs/windowsuseEffect(() => {if (typeof window === "undefined") {return;}const handleStorageChange = (e: StorageEvent | CustomEvent) => {if (e instanceof StorageEvent) {if (e.key === key && e.newValue) {try {setStoredValue(JSON.parse(e.newValue));} catch (error) {console.warn(`Error parsing localStorage key "${key}":`, error);}}} else {const { key: eventKey, value } = e.detail;if (eventKey === key) {setStoredValue(value);}}};window.addEventListener("storage", handleStorageChange);window.addEventListener("local-storage",handleStorageChange as EventListener);return () => {window.removeEventListener("storage", handleStorageChange);window.removeEventListener("local-storage",handleStorageChange as EventListener);};}, [key]);return [storedValue, setValue, removeValue];}
Examples
Store User Settings
interface UserSettings {notifications: boolean;language: string;fontSize: number;}function SettingsPanel() {const [settings, setSettings] = useLocalStorage<UserSettings>("user-settings",{notifications: true,language: "en",fontSize: 16,});const updateFontSize = (size: number) => {setSettings((prev) => ({ ...prev, fontSize: size }));};return (<div><label><inputtype="checkbox"checked={settings.notifications}onChange={(e) =>setSettings((prev) => ({...prev,notifications: e.target.checked,}))}/>Enable notifications</label><selectvalue={settings.fontSize}onChange={(e) => updateFontSize(Number(e.target.value))}><option value={14}>Small</option><option value={16}>Medium</option><option value={18}>Large</option></select></div>);}
Shopping Cart
function ShoppingCart() {const [cart, setCart, clearCart] = useLocalStorage<CartItem[]>("cart", []);const addToCart = (item: CartItem) => {setCart((prev) => [...prev, item]);};const removeFromCart = (id: string) => {setCart((prev) => prev.filter((item) => item.id !== id));};return (<div><h2>Cart ({cart.length} items)</h2>{cart.map((item) => (<CartItem key={item.id} item={item} onRemove={removeFromCart} />))}<button onClick={clearCart}>Clear Cart</button></div>);}
Form Draft
function BlogPostEditor() {const [draft, setDraft, removeDraft] = useLocalStorage("blog-draft", {title: "",content: "",tags: [],});const handlePublish = async () => {await publishPost(draft);removeDraft(); // Clear draft after publishing};return (<form><inputvalue={draft.title}onChange={(e) =>setDraft((prev) => ({ ...prev, title: e.target.value }))}placeholder="Title"/><textareavalue={draft.content}onChange={(e) =>setDraft((prev) => ({ ...prev, content: e.target.value }))}placeholder="Content"/><button type="button" onClick={handlePublish}>Publish</button></form>);}