99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

Optional Settings for the Zustand Persist Middleware (Using Storage)

ReactZustand
2 years ago
0

This post summarizes the optional settings you can use in Zustand's Persist middleware.
name is a required option.

All options are explained based on the following example.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { create } from 'zustand' import { combine, persist } from 'zustand/middleware' export const useCountStore = create( persist( combine( { count: 0, double: 0 }, set => ({ increase: () => set(state => ({ count: state.count + 1 })) }) ), { name: 'count' } ) )

For overall usage of Zustand, see Zustand Essentials!

# storage

Instead of the default local storage (localStorage), you can use other storage such as session storage or IndexedDB.
If you want your own custom storage, return an object that matches the StateStorage interface through the createJSONStorage helper function.

/node_modules/zustand/esm/middleware/persist.d.mts
TS
1
2
3
4
5
export interface StateStorage { getItem: (name: string) => string | null | Promise<string | null>; setItem: (name: string, value: string) => unknown | Promise<unknown>; removeItem: (name: string) => unknown | Promise<unknown>; }
StateStorage interface
/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
// ... export const useCountStore = create( persist( // combine(), { name: 'count', storage: createJSONStorage(() => sessionStorage) // Use session storage } ) )

# Partialize

To save only the state you want instead of saving all state to storage, use the partialize option to return an object that contains only the state to save.
Because it's a factory function, you can transform the state when saving, but until rehydration, the current state (currentState) and the storage state (persistedState) may differ.
Hydration is explained in the next option.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
// ... export const useCountStore = create( persist( // combine(), { name: 'count', partialize: currentState => ({ count: currentState.count // Save only the count property to storage! }) } ) )

# onRehydrateStorage

The onRehydrateStorage option is a function that runs when the storage is hydrated.
You can write logic that needs to run right after hydration, and the state parameter of onRehydrateStorage includes the actions.

In Zustand, hydration is the process of merging the state saved in storage with the current state.
You can also rehydrate manually at any time with the useCountStore.persist.rehydrate() method.
It's easiest to think of it as merging the storage state into the current state.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
// ... export const useCountStore = create( persist( // combine(), { name: 'count', onRehydrateStorage: state => { console.log('Merged with the storage state!', state) } } ) )

# skipHydration

You can skip the initial hydration with the skipHydration option.
You can then rehydrate manually with the useCountStore.persist.rehydrate() method.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
// ... export const useCountStore = create( persist( // combine(), { name: 'count', skipHydration: true } ) )
Doesn't hydrate the storage state.
/src/components/RehydrateButton.tsx
TSX
1
2
3
4
5
6
7
8
9
import { useCountStore } from './store/count' export default function RehydrateCountStore() { return ( <button onClick={() => useCountStore.persist.rehydrate()}> Rehydrate! </button> ) }
Hydrates when you click the button.

# version

If you introduce changes that differ significantly from the data already saved in storage, you can specify a new version as a number so that storage data with a mismatched version isn't used.
If you need to make use of the old version's data instead of ignoring it, you can also use the migrate option described next.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
// ... export const useCountStore = create( persist( // combine(), { name: 'count', version: 1 // Default: 0 } ) )

# migrate

The migrate function runs during hydration when the version of the current state differs from the version in storage.
The function must return state of a type that matches the latest version, and it's useful when you need to use the data in storage but its structure or names have changed.

/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { create } from 'zustand' import { combine, persist } from 'zustand/middleware' interface StateVersion0 { count: number double: number } interface StateVersion1 { amount: number multiplier: number max: number } export const useCountStore = create( persist( combine( { amount: 0, multiplier: 0 }, set => ({ increase: () => set(state => ({ amount: state.amount + 1 })) }) ), { name: 'count', version: 1, migrate: (persistedState, version) => { if (version === 0) { const oldState = persistedState as StateVersion0 const newState: StateVersion1 = { amount: oldState.count, multiplier: oldState.double, max: 100 } return newState } return persistedState } } ) )

# merge

The merge option lets you specify how the storage state is merged with the current state during hydration.
By default, the storage state and the current state are shallow merged, but if you need a deep merge, you can use Lodash's merge function.
It runs after the migrate function (when both options are set), and it receives the value returned by migrate as its first parameter (persistedState).

BASH
1
2
npm i lodash-es npm i -D @types/lodash-es
Install Lodash if you need a deep merge
/src/store/count.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ... import { merge } from 'lodash-es' export const useCountStore = create( persist( // combine(), { name: 'count', merge: (persistedState, currentState) => { return merge(currentState, persistedState) // Deep merge the current state and the storage state } } ) )