Skip to content
D
Documentation

Persist store data

how-to
2 min readUpdated

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:

bash
npm 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.

ts
import { 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:

tsx
import { 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.

OptionTypeDefaultWhat it does
namestringRequired; no defaultSets the unique storage key.
storagePersistStorage<{ bears: number }>createJSONStorage(() => localStorage)Selects the storage adapter used to read and write state.
partialize(state: BearStore) => { bears: number }(state) => stateFilters 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.

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?