Merge partial persisted state
Use a custom merge function when persisted data contains only part of a nested object and hydration must keep the fields already present in the current store.
Build the store with create and wrap its state creator with persist.
When to use it
persist hydrates the store by merging the stored value with the current state. Its default merge is shallow: if storage contains settings: { theme: 'dark' }, it replaces the complete current settings object, including fields such as density. Supply a nested merge when the persisted object is partial.
Example
This complete React example hydrates a partial settings object and keeps the current density field.
tsximport { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { createRoot } from 'react-dom/client'
type Settings = {
theme: 'light' | 'dark'
density: 'compact' | 'comfortable'
}
type Store = {
settings: Settings
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function getTheme(value: unknown): Settings['theme'] | undefined {
if (!isRecord(value) || !isRecord(value.settings)) return undefined
return value.settings.theme === 'light' || value.settings.theme === 'dark'
? value.settings.theme
: undefined
}
localStorage.setItem(
'settings',
JSON.stringify({ state: { settings: { theme: 'dark' } }, version: 0 }),
)
const useStore = create<Store>()(
persist(
() => ({ settings: { theme: 'light', density: 'comfortable' } }),
{
name: 'settings',
merge: (persistedState, currentState) => {
const theme = getTheme(persistedState)
return {
...currentState,
settings: {
...currentState.settings,
...(theme === undefined ? {} : { theme }),
},
}
},
},
),
)
function SettingsView() {
const settings = useStore((state) => state.settings)
return <p>{settings.theme} / {settings.density}</p>
}
const root = document.getElementById('root')
if (root !== null) createRoot(root).render(<SettingsView />)
After hydration, the mounted component displays dark / comfortable: the persisted theme applies and the current density remains.
Options that matter here
| Option | Type | Default | What it does |
|---|---|---|---|
name | string | — | Names the storage entry. Use a unique name for the store. |
partialize | (state: State) => Object | (state) => state | Selects the fields written to storage. |
merge | (persistedState: unknown, currentState: State) => State | Shallow merge | Combines stored data with current state during hydration. |
Pitfalls
- A custom nested merge is needed when partial persistence targets nested objects. The default shallow merge can erase fields that are present only in the current nested object.
- Async persisted storage hydrates after the initial render, so the UI can show default state temporarily. Wait for hydration when that timing matters; see Control persisted hydration.
Live demo
Explore the Zustand demo for a mounted store application.
Was this page helpful?