championswimmer/vuex-persist

When is the plugin ready to be used?

PierBover opened this issue ยท 13 comments

Loading from localStorage seems immediate when the application starts, but when writing to the vuex store persistence doesn't seem to work until some time has passed.

If I do this in root component the state in localStorage doesn't change:

const root = new Vue({
	el: '#app',
	router,
	store,
	render: (h) => h(App),
	mounted () {
		store.commit('persistent/SET_TEST_VAR', true);
		console.log(store.state.persistent.test_var);
	}
});

If I do this instead it works fine:

setTimeout(() => {
	store.commit('persistent/SET_TEST_VAR', true);
	console.log(store.state.persistent.test_var);
}, 1000);

I managed to find a workaround if you're using VueRouter

Given you have the strict RESTORE_MUTATION setup;

// ./store/index.js
import VuexPersistPatch from './plugins/vuex-persist-patch'

export default new Vuex.Store({
  mutations: Object.assign(mutations, {
    RESTORE_MUTATION: vuexLocal.RESTORE_MUTATION
  }),
  plugins: [
    vuesLocal.plugin,
    VuexPersistPatch()
  ]
})
// ./plugins/vuex-persist-patch.js
export default function vuexPersistPatch () {
  return store => {
    store._vm.$root.$data['vue-persist-patch-delay'] = true

    store.subscribe(mutation => {
      if (mutation.type === 'RESTORE_MUTATION') {
        store._vm.$root.$data['vue-persist-patch-delay'] = false

        store._vm.$root.$emit('storageReady')
      }
    })
  }
}

Take special note to using store._vm to receive the event from the bus.

// ./router/index.js
import store from '../store'

router.beforeEach((to, from, next) => {
  if (store._vm.$root.$data['vue-persist-patch-delay']) {

    // Hold the request, until the Storage is complete.
    store._vm.$root.$on('storageReady', () => {
      next({path: to.path, query: to.query, params: to.params})
    })
  } else {
    // YOUR OTHER RULES GO HERE
  }
})

Thanks @kylewelsby for the suggestion.

Seems overly complicated though since localStorage interactions are not async.

I ended up using vuex-persistedstate which works as expected.

The problem has been resolved in v1.0

If the store is not async (you're using localStorage), then vuex-persist will synchrnously restore state and you won't feel like "The store is not immediately restored"

@championswimmer I've just tested out the changes in version 1.0 and it seems as though your fix for this behavior does not apply when using sessionStorage() instead of localStorage().
Would it be possible to make the two types of storage behave the same, sync way, or is there something that would prevent someone from doing that? It'd really helpe if sessionStorage() could also work this way.
Thanks for your work on this project :)

CosX commented

Thank you @PierBover. I managed to do this with localforage inspired by your plugin.

//first define a plugin that emits when the state has been persisted
const vuexPersistEmitter = () => {
    return store => {
        store.subscribe(mutation => {
            if (mutation.type === 'RESTORE_MUTATION') {
                store._vm.$root.$emit('storageReady');
            }
        });
    }
};

//I only needed to define strict mode here and not in the store.
const vuexLocal = new VuexPersistence({
    strictMode: true,
    asyncStorage: true,
    storage: localforage
});

export default new Vuex.Store({
   ...,
    mutations: {
        RESTORE_MUTATION: vuexLocal.RESTORE_MUTATION
    },
    plugins: [
        vuexLocal.plugin,
        vuexPersistEmitter()
      ]
});

And then in any kind of component:

const app = new Vue({
    store,
    ...App,
    mounted() {
        const self = this;
        self.$store._vm.$root.$on('storageReady', () => {
            self.$store.dispatch('somethingThatDependsOnTheStateBeingFilled');
        });
    }
});

Hi, thanks for this plugin ๐Ÿ‘ using custom methods for the storage (setItem, getItem, etc) instead of localforage will fail unless the flag here is set too

Tried this plugin with 1.0.0 and "The store is not immediately restored". Is there a fix of this in any of the new versions or do I implement a workaround?

Tries this plugin with 1.1.5 and still the same issue. using @CosX workaround does help but only if the state is restored....so I have to check the regular way and via store events

created(){
      this.setFetching({fetching: true})
      this.$store._vm.$root.$on('storageReady', () => {
        this.setGameStates()
        this.setFetching({fetching: false})
      });
    },
    mounted() {
      this.setGameStates()
    }

Just a note if you are having this timing issue on production mode and is trying to use the subscribe trick.
As you will have strict set to false, the subscribe with RESTORE_MUTATION won't work.
That's because the store won't commit, but rather replace the state.

Look the code here:

vuex-persist/src/index.ts

Lines 163 to 167 in a5033b5

if (this.strictMode) {
store.commit('RESTORE_MUTATION', savedState)
} else {
store.replaceState(merge(store.state, savedState))
}

@championswimmer Can we have some event emitted here to know when that has happened on production mode?

EDIT:
Based on #3 (comment),
I have made this to be notified when storage has been replaced:

import store from '@/store' //<-- your store module

export default new VuexPersistence({
  storage: localForage,
  strictMode: false,
  asyncStorage: true,
  restoreState: (key, storage) => {
    return new Promise(resolve => {
      storage
        .getItem(key)
        .then(data => {
          resolve(data);
          store._vm.$f7.emit('storageReady');
        })
    })
  },
  ...
});

I managed to find a workaround if you're using VueRouter

Given you have the strict RESTORE_MUTATION setup;

// ./store/index.js
import VuexPersistPatch from './plugins/vuex-persist-patch'

export default new Vuex.Store({
  mutations: Object.assign(mutations, {
    RESTORE_MUTATION: vuexLocal.RESTORE_MUTATION
  }),
  plugins: [
    vuesLocal.plugin,
    VuexPersistPatch()
  ]
})
// ./plugins/vuex-persist-patch.js
export default function vuexPersistPatch () {
  return store => {
    store._vm.$root.$data['vue-persist-patch-delay'] = true

    store.subscribe(mutation => {
      if (mutation.type === 'RESTORE_MUTATION') {
        store._vm.$root.$data['vue-persist-patch-delay'] = false

        store._vm.$root.$emit('storageReady')
      }
    })
  }
}

Take special note to using store._vm to receive the event from the bus.

// ./router/index.js
import store from '../store'

router.beforeEach((to, from, next) => {
  if (store._vm.$root.$data['vue-persist-patch-delay']) {

    // Hold the request, until the Storage is complete.
    store._vm.$root.$on('storageReady', () => {
      next({path: to.path, query: to.query, params: to.params})
    })
  } else {
    // YOUR OTHER RULES GO HERE
  }
})

@kylewelsby Thank you !! this served me!

PR #107 is intended to address the issue raised by @rulrok. I've tested locally and found that I'm able to consistently get vuex-persist to restore the state before any other mutations are committed.