# Persist store data

Use [`persist`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#persist) when a store must retain its state across page reloads. Give the store a unique `name`, choose a storage adapter, and account for the time at which hydration completes.

## Create a persisted React store

Wrap the state creator passed to [`create`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#create) with `persist`. This example stores the bear count in `sessionStorage`; the mounted component displays the count and updates it when you click the button.

```tsx title="bear-store.ts"
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'

type BearStore = {
  bears: number
  addBear: () => void
}

export const useBearStore = create<BearStore>()(
  persist(
    (set) => ({
      bears: 0,
      addBear: () => set((state) => ({ bears: state.bears + 1 })),
    }),
    {
      name: 'bear-counter',
      storage: createJSONStorage(() => sessionStorage),
    },
  ),
)
```

```tsx title="BearCounter.tsx"
import { useBearStore } from './bear-store'

export function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  const addBear = useBearStore((state) => state.addBear)

  return (
    <button type="button" onClick={addBear}>
      Bears: {bears}
    </button>
  )
}
```

`name` is the storage key and is the only required persistence option, so use a different value for each store. If you omit `storage`, persistence uses JSON storage backed by `localStorage`. [`createJSONStorage`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#createjsonstorage) converts a storage engine with `getItem`, `setItem`, and `removeItem` methods into the adapter expected by `persist`.

## Supply a custom storage engine

Implement [`StateStorage`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#statestorage) when the storage engine needs its own key handling or API. The adapter below prefixes every key while retaining the browser's synchronous storage behaviour. The storage getter is a function, so the adapter can obtain a browser storage object only when the store is created.

```tsx title="prefixed-bear-store.ts"
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
import type { StateStorage } from 'zustand/middleware'

type BearStore = {
  bears: number
  addBear: () => void
}

const prefixedStorage: StateStorage = {
  getItem: (name) => sessionStorage.getItem(`demo:${name}`),
  setItem: (name, value) => sessionStorage.setItem(`demo:${name}`, value),
  removeItem: (name) => sessionStorage.removeItem(`demo:${name}`),
}

export const useBearStore = create<BearStore>()(
  persist(
    (set) => ({
      bears: 0,
      addBear: () => set((state) => ({ bears: state.bears + 1 })),
    }),
    {
      name: 'bear-counter',
      storage: createJSONStorage(() => prefixedStorage),
    },
  ),
)
```

The adapter receives JSON strings from `createJSONStorage`; do not parse or stringify them again in these methods. The JSON helper uses `JSON.parse` and `JSON.stringify` without runtime shape validation, so validate untrusted or stale data in a custom adapter before treating it as store state.

## Handle asynchronous hydration

`localStorage` and `sessionStorage` are synchronous. An asynchronous adapter returns a promise from `getItem`, so the first render can show the store's defaults while persisted data is loading. Wait for hydration before presenting UI that depends on the persisted value.

```tsx title="async-bear-store.ts"
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
import type { StateStorage } from 'zustand/middleware'

type BearStore = {
  bears: number
  addBear: () => void
}

const asyncStorage: StateStorage<Promise<void>> = {
  getItem: async (name) => {
    await new Promise<void>((resolve) => window.setTimeout(resolve, 100))
    return sessionStorage.getItem(name)
  },
  setItem: async (name, value) => {
    sessionStorage.setItem(name, value)
  },
  removeItem: async (name) => {
    sessionStorage.removeItem(name)
  },
}

export const useAsyncBearStore = create<BearStore>()(
  persist(
    (set) => ({
      bears: 0,
      addBear: () => set((state) => ({ bears: state.bears + 1 })),
    }),
    {
      name: 'async-bear-counter',
      storage: createJSONStorage(() => asyncStorage),
    },
  ),
)
```

Register an `onFinishHydration` listener in the component and remove it when the component unmounts. [`useStore`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#usestore) reads the store's state; the listener changes the component's local `hydrated` flag when the persisted state has been merged.

```tsx title="AsyncBearCounter.tsx"
import { useEffect, useState } from 'react'
import { useStore } from 'zustand'

import { useAsyncBearStore } from './async-bear-store'

export function AsyncBearCounter() {
  const bears = useStore(useAsyncBearStore, (state) => state.bears)
  const addBear = useStore(useAsyncBearStore, (state) => state.addBear)
  const [hydrated, setHydrated] = useState(() =>
    useAsyncBearStore.persist.hasHydrated(),
  )

  useEffect(() => {
    const unsubscribe = useAsyncBearStore.persist.onFinishHydration(() => {
      setHydrated(true)
    })

    return unsubscribe
  }, [])

  if (!hydrated) {
    return <p>Loading saved bears…</p>
  }

  return (
    <button type="button" onClick={addBear}>
      Bears: {bears}
    </button>
  )
}
```

![The mounted counter displays the persisted bear count after hydration.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/cb87993b983153eadb65eff5d2ac060c.png)

The loading message can appear on the initial render with asynchronous storage. After hydration finishes, the component renders the persisted count. The `onFinishHydration` method returns the unsubscribe function, so the listener does not outlive the component.

## Options that matter here

| Option | Type | Default | What it does |
|---|---|---|---|
| `name` | `string` | — | Selects the unique key used in storage. |
| `storage` | storage adapter | `createJSONStorage(() => localStorage)` | Reads and writes the persisted value through a storage adapter. |
| `onRehydrateStorage` | function or function returning a function | — | Runs custom logic before and after hydration; the returned callback receives the state or an error. |
| `skipHydration` | `boolean \| undefined` | `undefined` | Prevents automatic initial hydration, leaving the first call to `rehydrate()` to the application. |

Use `skipHydration` when the application controls when hydration begins, such as an SSR setup. Otherwise, let `persist` hydrate on initialization and gate asynchronous-storage UI as shown above.

## Related

- [Persist selected state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/persist-selected-state) filters which fields are stored.
- [Merge persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/merge-persisted-state) handles nested objects that need more than the default shallow merge.
- [Control persisted hydration](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/control-persisted-hydration) covers manual hydration control.
- [Migrate persisted state](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/migrate-persisted-state) handles versioned stored data.
- See the [persist middleware reference](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand-middleware#persist) for the complete API.
