Use persist when a store's data needs to survive a page reload. Wrap your state creator, choose a unique storage key, and use partialize to select the fields to save.
The browser example below keeps a bear count across reloads while a temporary click count resets to zero. It uses React and the browser's localStorage.
1. Create a persisted store
In your React TypeScript project, install Zustand:
bashnpm install zustand
Pass the wrapped state creator to create. persist returns a state creator; create returns the hook your components use. Use createJSONStorage to adapt browser storage to JSON reads and writes.
tsimport { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
type BearStore = {
bears: number
temporaryClicks: number
addABear: () => void
}
export const useBearStore = create<BearStore>()(
persist(
(set) => ({
bears: 0,
temporaryClicks: 0,
addABear: () =>
set((state) => ({
bears: state.bears + 1,
temporaryClicks: state.temporaryClicks + 1,
})),
}),
{
name: 'bear-counter-storage',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({ bears: state.bears }),
},
),
)
name is the storage key and the only required option. Give each persisted store a unique key so stores do not share the same storage entry.
partialize picks the data written to storage, not the fields available to components. Here, the live store still contains temporaryClicks and addABear, but the persisted state contains only bears. Each action update writes the selected state under bear-counter-storage.
2. Render and test the counter
Select the values and action separately, following the React starter's counter pattern. Use this file as your browser entry point:
tsximport { createRoot } from 'react-dom/client'
import { useBearStore } from './store'
function App() {
const bears = useBearStore((state) => state.bears)
const temporaryClicks = useBearStore((state) => state.temporaryClicks)
const addABear = useBearStore((state) => state.addABear)
return (
<main style={{ minHeight: 400 }}>
<h1>Persisted bear counter</h1>
<p>Bears: {bears}</p>
<p>Clicks since reload: {temporaryClicks}</p>
<button onClick={addABear}>Add a bear</button>
</main>
)
}
createRoot(document.getElementById('root')!).render(<App />)
Use the HTML setup from React quick start, with the script's src set to /main.tsx to load the entry point above.
Click Add a bear: both counts increase. Reload the page: the bear count returns from storage, and Clicks since reload shows zero. Rehydration merges the saved fields into the initial store, retaining the action and the initial value for the omitted field.
3. Choose local or session storage
For localStorage, keep the sample's storage option, or omit it to use the default adapter. For sessionStorage, replace that option with storage: createJSONStorage(() => sessionStorage). The same counter and partialize function work with either storage engine.
Both browser storage engines are synchronous, so Zustand hydrates during store creation. Use sessionStorage for data scoped to the browser page session; use localStorage when the data needs to remain after that session ends.
Options that matter
The types below describe the sample's full BearStore and selected persisted state. A storage adapter implements PersistStorage.
| Option | Type | Default | What it does |
|---|---|---|---|
name | string | Required; no default | Sets the unique storage key. |
storage | PersistStorage<{ bears: number }> | createJSONStorage(() => localStorage) | Selects the storage adapter used to read and write state. |
partialize | (state: BearStore) => { bears: number } | (state) => state | Filters the state before each write. |
Pitfalls
Asynchronous storage does not hydrate before the initial render. Defaults can therefore look like persisted values—for example, a logged-out state—while storage is still loading. Track hydration completion and wait before rendering UI that depends on persisted data. See Control persistence hydration for the hydration gate.
For partially persisted nested objects and custom merging, see Migrate persisted state. For server-rendered React applications, see Next.js store setup.
Live demo and related
Try the live Zustand demo for the hook-based counter interaction. The example on this page adds persistence to that pattern.
Was this page helpful?