Skip to content

Commit

Permalink
Merge pull request #118 from morphatic/emit_restored
Browse files Browse the repository at this point in the history
Emit `vuexPersistStateRestored` event when async store replaced (addresses #15)

Co-authored-by: Morgan Benton <[email protected]>
  • Loading branch information
championswimmer and morphatic authored Aug 30, 2019
2 parents 0f37510 + eaf3806 commit 0ff892e
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 6 deletions.
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,13 +428,39 @@ You can note 2 differences here -
1. All functions are asynchronous with Promises (because WebSQL and IndexedDB are async)
2. It works on objects too (not just strings)

I have made `vuex-persist` compatible with both types of storages, but this comes
at a slight cost.
I have made `vuex-persist` compatible with both types of storages, but this comes at a slight cost.
When using asynchronous (promise-based) storages, your state will **not** be
immediately restored into vuex from localForage. It will go into the event loop
and will finish when the JS thread is empty. This can invoke a delay of few seconds.
[Issue #15](https://github.com/championswimmer/vuex-persist/issues/15) of this repository explains
what you can do to _find out_ when store has restored.

### How to know when async store has been replaced

As noted above, the store is not immediately restored from async stores like localForage. This can have the unfortunate side effect of overwriting mutations to the store that happen before `vuex-persist` has a chance to do its thing. In strict mode, you can create a plugin to subscribe to **`RESTORE_MUTATION`** so that you tell your app to wait until the state has been restored before committing any further mutations. ([Issue #15 demonstrates how to write such a plugin.](https://github.com/championswimmer/vuex-persist/issues/15)) However, since you should turn strict mode off in production, and since [`vuex` doesn't currently provide any kind of notification when `replaceState()` has been called](https://github.com/vuejs/vuex/issues/1316), starting with `v2.1.0` `vuex-persist` will add a `restored` property to the `store` object to let you know the state has been restored and that it is now safe to commit any mutations that modify the stored state. `store.restored` will contain the Promise returned by calling the async version of `restoreState()`.

Here's an example of a `beforeEach()` hook in `vuex-router` that will cause your app to wait for `vuex-persist` to restore the state before taking any further actions:

```js
// in src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import { store } from '@/store' // ...or wherever your `vuex` store is defined

Vue.use(Router)

const router = new Router({
// define your router as you normally would
})

const waitForStorageToBeReady = async (to, from, next) => {
await store.restored
next()
}
router.beforeEach(waitForStorageToBeReady)

export default router
```

Note that on the 2nd and subsequent router requests to your app, the Promise in `store.restored` should already be in a "resolved" state, so the hook will _not_ force your app to wait for additional calls to `restoreState()`.

## Unit Testing

Expand Down
12 changes: 10 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,16 @@ export class VuexPersistence<S> implements PersistOptions<S> {
* @param {Store<S>} store
*/
this.plugin = (store: Store<S>) => {
((this.restoreState(this.key, this.storage)) as Promise<S>).then((savedState) => {
/**
* For async stores, we're capturing the Promise returned
* by the `restoreState()` function in a `restored` property
* on the store itself. This would allow app developers to
* determine when and if the store's state has indeed been
* refreshed. This approach was suggested by GitHub user @hotdogee.
* See https://github.com/championswimmer/vuex-persist/pull/118#issuecomment-500914963
* @since 2.1.0
*/
(store as any).restored = ((this.restoreState(this.key, this.storage)) as Promise<S>).then((savedState) => {
/**
* If in strict mode, do only via mutation
*/
Expand All @@ -168,7 +177,6 @@ export class VuexPersistence<S> implements PersistOptions<S> {
} else {
store.replaceState(merge(store.state, savedState || {}))
}

this.subscriber(store)((mutation: MutationPayload, state: S) => {
if (this.filter(mutation)) {
this._mutex.enqueue(
Expand Down
60 changes: 60 additions & 0 deletions test/async-plugin-emits-restored.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Created by morphatic on 23/05/19. Updated 12/06/19.
*/

import localForage from 'localforage'
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from '..'

const objectStore: {[key: string]: any} = {}
/* tslint:disable:no-empty */
const MockForageStorage = {
_driver: 'objectStorage',
_support: true,
_initStorage() {},
clear() {},
getItem<T>(key: string): Promise<T> {
return Promise.resolve<T>(objectStore[key])
},
iterate() {},
key() {},
keys() {},
length() {},
removeItem() {},
setItem<T>(key: string, data: T): Promise<T> {
return Promise.resolve<T>((objectStore[key] = data))
}
}
/* tslint:enable:no-empty */

Vue.use(Vuex)

localForage.defineDriver(MockForageStorage as any)
localForage.setDriver('objectStorage')

const vuexPersist = new VuexPersistence<any>({
key: 'restored_test',
asyncStorage: true,
storage: localForage,
reducer: (state) => ({ count: state.count })
})

const storeOpts = {
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
plugins: [vuexPersist.plugin]
}

describe('Storage: AsyncStorage; Test: set `restored` on store; Strict Mode: OFF', () => {
it('connects the `store.restored` property to the Promise returned by `restoreState()`', (done) => {
const store: any = new Vuex.Store<any>(storeOpts)
store.restored.then(done)
})
})
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"outDir": "dist",
"declaration": true,
"declarationDir": "dist/types",
"noImplicitReturns": true,
"noImplicitAny": true,
Expand Down

0 comments on commit 0ff892e

Please sign in to comment.