useLocalStorage
A hook that persists state to localStorage and syncs it across tabs.
0
Reload the page, or open this page in a second tab, and the value follows.
Installation
Usage
State that survives a reload, shaped like useState.
import { useLocalStorage } from "@/hooks/use-local-storage";
function SidebarToggle() {
const [collapsed, setCollapsed, reset] = useLocalStorage("sidebar:collapsed", false);
return <button onClick={() => setCollapsed((previous) => !previous)}>{String(collapsed)}</button>;
}The tuple is [value, setValue, remove]. setValue takes a value or an updater; remove deletes
the key and returns the hook to defaultValue.
Staying in sync
Every component reading the same key stays in sync, in two directions:
- Across tabs, through the native
storageevent. - Within a tab, through a scoped event the hook dispatches on write — because
storagedoes not fire in the tab that made the change.
So two sidebars, two tabs, and a background write all converge on the same value without a store.
Server rendering
The hook reads through useSyncExternalStore with a server snapshot of null, so the first render
returns defaultValue on both sides and hydration never mismatches. The stored value appears
immediately after mount.
Notes
- Values are serialized with
JSON.stringify/JSON.parse. Passserializeanddeserializefor anything else, such asDateorMap. - Unreadable, unwritable, and unparseable storage all fall back to
defaultValuerather than throwing, which covers private browsing modes, disabled storage, quota limits, and values written by an older version of your app. defaultValueanddeserializeparticipate in memoization. Hoist them to module scope, or memoize them, if you pass non-primitives.