useLocalStorage
State persisted in localStorage
From the Custom Hooks collection. Works like useState, but mirrors the
value to localStorage — it survives a page reload (theme, preferences, drafts).
import { useState } from "react";
const useLocalStorage = <T>(key: string, initial: T) => {
// Lazy init: reads the storage only once, on mount.
const [stored, setStored] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initial;
} catch {
return initial; // SSR or storage unavailable.
}
});
const setValue = (value: T | ((prev: T) => T)) => {
setStored((prev) => {
const next = value instanceof Function ? value(prev) : value;
try {
window.localStorage.setItem(key, JSON.stringify(next));
} catch {
// Quota full or private mode: keep it in memory only.
}
return next;
});
};
return [stored, setValue] as const;
};
export default useLocalStorage;
⚠️ The try/catch covers SSR (Next.js: window doesn’t exist on the server) and
private mode. It accepts a functional updater (setValue(v => ...)), just like useState.
Example — toggle a theme and remember the choice:
const [theme, setTheme] = useLocalStorage("theme", "light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
Related: Custom Hooks · use-media-query.