usePersistedState React hook in TypeScript
v1.0.0Added in 1.0.0
A React useState that survives reloads in localStorage, sessionStorage or a cookie, syncs across tabs and hydrates cleanly.
Switch the listing view in either card: the other card follows at once because both read the same key. Type a message: it is saved as you type. Press Reload to remount the demo and watch every value come back, or Delete draft to forget the draft. Open the page in a second tab and changes in local storage follow you there.
usePersistedState is a Pro React hook in the React hooks and utilities category, written in TypeScript for React 19. It installs as source with npx wingo-ui@latest add use-persisted-state, together with useControllableState.
Add to a project
Requirements: React 19, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add use-persisted-stateusePersistedState is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import usePersistedState from @/hooks/use-persisted-state.
import { usePersistedState } from "@/hooks/use-persisted-state"
export function ListingsView() {
const [view, setView] = usePersistedState<"list" | "grid">("listings:view", "list")
return (
<div role="group" aria-label="View" className="inline-flex gap-1">
<button type="button" aria-pressed={view === "list"} onClick={() => setView("list")}>List</button>
<button type="button" aria-pressed={view === "grid"} onClick={() => setView("grid")}>Grid</button>
</div>
)
}API
key*default"ui-lab:preferences"string | null | undefinedusePersistedState(key, defaultValue, options?): the storage key. null or an empty string turns persistence off (plain useState), so a component can take an optional persistKey prop.
defaultValue*TThe value on the server, during hydration and whenever nothing is stored. The stored value replaces it right after hydration, so markup never mismatches.
storagedefault"local""local" | "session" | "cookie"Where the value lives. cookie is the one a server can read: a Next layout reads cookies().get(persistedCookieName(key)) and passes the value as defaultValue, so a collapsed sidebar is collapsed on the first paint.
syncTabsdefaulttruebooleanFollow changes made in other tabs (local storage only). Instances on the same page always stay in sync.
serialize / deserialize(value: T) => string / (raw: string) => THow the value becomes a string and back. Default JSON; a stored value that fails to parse falls back to defaultValue.
maxAgedefault31536000numberCookie lifetime in seconds (one year).
returns[value: T, setValue: (next: SetStateAction<T>) => void, meta: { hydrated: boolean; remove(): void }]Like useState. meta.hydrated is false on the server and during hydration, so a component can skip the animation of the restore that follows. meta.remove forgets the stored value. Storage that throws (private mode, a full quota, blocked cookies) keeps working in memory for the session.
Parts
usePersistedControllableState
hookThe controlled / uncontrolled pattern of useControllableState with the uncontrolled value persisted: collapsed / defaultCollapsed / onCollapsedChange plus persistKey in one line. A controlled value is never written to storage. Signature: ({ key, value?, defaultValue, onChange?, ...options }) => [T, setValue, meta].
readPersisted
functionReads the raw stored string outside React (event handlers, other stores). Signature: (key, storage?) => string | null.
writePersisted
functionWrites or removes (null) the raw string outside React; every hook reading the key updates. Signature: (key, raw | null, storage?, maxAge?) => void.
persistedCookieName
functionThe cookie name a key is stored under, for server code: cookies().get(persistedCookieName(key)). Signature: (key) => string.
PersistedStorage
type"local" | "session" | "cookie", where the value lives.
UsePersistedStateOptions
typeThe options object: storage, serialize, deserialize, maxAge and syncTabs.
PersistedStateMeta
typeThe third tuple item: { hydrated: boolean; remove(): void }.
UsePersistedControllableStateParams
typeThe options of usePersistedControllableState: key, value, defaultValue, onChange plus the storage options.
Examples
Remembered settings
Two switches kept in local storage. Flip them, press Reload, and they come back as you left them.
Draft for this tab only
storage="session" keeps an unsent message until the tab closes.
Cookie the server can read
storage="cookie" so a layout can render the saved view on the first paint.
Dependencies
Files
- hooks/use-persisted-state.ts
FAQ
Is the usePersistedState hook free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install usePersistedState?
Run npx wingo-ui@latest add use-persisted-state. It also adds the Wingo UI item it builds on (useControllableState). With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/use-persisted-state.



