mwui
GitHub

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 storage event.
  • Within a tab, through a scoped event the hook dispatches on write — because storage does 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. Pass serialize and deserialize for anything else, such as Date or Map.
  • Unreadable, unwritable, and unparseable storage all fall back to defaultValue rather than throwing, which covers private browsing modes, disabled storage, quota limits, and values written by an older version of your app.
  • defaultValue and deserialize participate in memoization. Hoist them to module scope, or memoize them, if you pass non-primitives.