Persisting State
StateManager
and AsyncStateManager
provides three lifecycle hooks:
init
— triggers when the State Manager is instantiateddidSet
— triggers each time the state is changed with.set
, even if the value remains the samedidReset
— triggers each time.reset
is called
These lifecycle hooks are are independent of each other and completely optional, but as far as lifecycle management is concerned, it is generally recommended to use all three.
Persisting & resetting state
To begin, we can start by defining didSet
and didReset
. By doing so, we would have a better idea of how the state is being persisted, which in return makes it easier for us to figure out how to initialize the state. In this example, we will serialize the state using JSON.stringify
and save it to localStorage
.
const UserState = new StateManager({
firstName: 'John',
lastName: 'Smith',
luckyNumber: 42,
}, {
lifecycle: {
didSet({ state, defaultState }) {
localStorage.removeItem('key', JSON.stringify(state))
},
didReset() {
localStorage.removeItem('key')
},
},
})
Do not call .set
inside the didSet
lifecycle hook as this will result in infinite recursion. The same applies to calling .reset
in the didReset
lifecycle hook and calling .init
in the init
lifecycle hook.
Retrieving state
Now that we know how the state is being persisted, all we need to do is to parse it back with JSON.parse
in the init
lifecycle hook.
const UserState = new StateManager({
firstName: 'John',
lastName: 'Smith',
luckyNumber: 42,
}, {
lifecycle: {
init({ commit }) {
const persistedState = JSON.parse(localStorage.getItem('key'))
commit(persistedState)
},
didSet({ state, defaultState }) {
localStorage.removeItem('key', JSON.stringify(state))
},
didReset() {
localStorage.removeItem('key')
},
},
})
Putting everything together
The code in the section above is a simple example of we can persist data and use it to initialize a State Manager. In reality, however, it could be a little bit more complicated. The code below shows what it might look like in a more realistic scenario.
const UserState = new StateManager({
firstName: 'John',
lastName: 'Smith',
luckyNumber: 42,
}, {
// 0. Do not run any lifecycle hooks in server environment
// (for projects that uses server-side rendering)
lifecycle: typeof window === 'undefined' ? {} : {
init({ commit, commitNoop, defaultState }) {
// 1. Get raw (string) state.
const rawState = localStorage.getItem('key')
if (rawState) {
try {
// 2. If it exists, attempt to parse it.
const persistedState = JSON.parse(rawState)
// 3. Commit the state by merging the default and the persisted states
// using the spread operator.
commit({
...defaultState,
...persistedState,
})
// 4. Initialization has completed,
// execution of the function can be ended early.
return
} catch (error) {
// 5. try-catch is used in case of corrupted JSON string.
console.error(error)
}
}
commitNoop() // pronounced as "commit no-op"
// 6. This indicates that initialization has been completed but without
// the need to change the state (state remains to be the default value).
},
didSet({ state, defaultState }) {
localStorage.removeItem('key', JSON.stringify(state))
},
didReset() {
localStorage.removeItem('key')
},
},
})