Zustand is a state-management solution for React apps. For anyone looking into a state manager that is low on boilerplate, very intuitive, and highly performant then I highly recommend using it. I personally love it.
This guide assumes you have some knowledge of the basics of Zustand.
Recapping LocalStorage Persistence
Baked into the Zustand API is a middleware that allows persisting the store to local storage. An example of how this persistence would look like (example taken straight from the docs):
export const useStore = create(persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 })
}),
{
name: "food-storage", // unique name
getStorage: () => sessionStorage, // (optional) by default the 'localStorage' is used
}
))
The persist function wraps the store and automatically sets the values inside the local storage. The entirety of the store can be identified in the local storage by a key (name) and a version (number) that can also be set in the options. These options can be set in the persist function; it first receives the Zustand store and the second parameter is the aforementioned configuration object.
An issue that can pop up while creating a store that is persisted to local storage is that the structure of the store can change in an update to the application. This can cause inconsistencies between what the store expects and what is currently persisted.
This can, in the worst-case scenarios, cause errors that cause the application to crash. Yikes! In order to circumvent this problem, Zustand offers a migration function to transition a persisted store to the new version.
Scenario
For example, let’s assume that our store currently looks something like this:
const AVAILABLE_FISHES = [
{
id: 1,
name: 'Tuna',
},
{
id: 2,
name: 'Goldfish',
}
]
export const useStore = create(persist(
(set, get) => ({
fishes: [{
id: 1,
name: 'Tuna'
}],
addAFish: () => set({ fishes: get().fishes + 1 })
}),
{
name: "food-storage", // unique name
}
))
Where our fishes key in the state should directly link up to a fish that exist in the AVAILABLE_FISHES constant.
However, we have a problem, if the object structure of the fish we save ever changes then the corresponding object in the persisted store will not update. For example, if our AVAILABLE_FISHES constant now includes the color:
const FISHES = [
{
id: 1,
name: 'Tuna',
color: 'Blue',
},
{
id: 2,
name: 'Goldfish',
color: 'Gold',
}
]
The object saved in the fishes key no longer has all the information necessary. This can be easily remedied by migrating the store to a new structure and version.
Migration
Initially, the version of the local storage is set to 0. This can be confirmed by opening up the dev tools and looking at the entry in the local storage.
In order for Zustand to detect a new store version, it must be set inside the persist configuration object.
export const useStore = create(persist(
(set, get) => ({
fishes: [{
id: 1,
name: 'Tuna'
}],
addAFish: () => set({ fishes: get().fishes + 1 })
}),
{
name: "food-storage", // unique name
version: 1,
migrate: (persistedState) => {
// Migrate store here...
}
}
))
Once Zustand detects that version store 1 is superior to the persisted store 0 then it will try to migrate the store with the provided function.
This function receives the persisted local storage state as its parameter and expects a new store to be returned.
Returning to our example we should link up our store exclusively to the ID and not the whole fish object.
migrate: (persistedState) => {
const oldFishes = persistedState.fishes;
const newFishes = oldFish.map((oldFish) => {
return oldFish.id;
})
return newFishes;
}
And with this the new object structure is correct and as soon as a user loads up the webpage it will automatically migrate its store to the new version.
Any time a new change must be made it can easily be done by raising the version and updating the migrate function.
And that’s it! Did you find this information useful? Have you been able to migrate the Zustand store? Let me know in the comments below.
Originally published at https://relatablecode.com on August 19, 2021.
Top comments (1)
What about multiple version skip/difference on the client side? Does it provide some "playback" by default - for example from the 0 to 5 version, migrating all steps, one by one? Or how it handles this situation please?