Search code examples
vue.jsvuex

Is there a way to know when the replaceState function of vuex store is called?


I'm using vuex-presistedstate in my project. In the source code on github the plugin calls store.replaceState to hydrate store from storage. Is there a way to know when the store hydrates?


Solution

  • The vuex-presistedstate plugin has a configuration option called rehydrated that allows you to pass a function that will be called immediately after replaceState. If you only care about calls to replaceState from that plugin then that should fit your needs nicely.

    I don't believe the store itself provides a 'hook' for when replaceState is called. The method replaceState is implemented here:

    https://github.com/vuejs/vuex/blob/e0126533301febf66072f1865cf9a77778cf2176/src/store.js#L183

    As you can see from the code it doesn't do much. Even subscribers registered using subscribe aren't called. However, you could potentially use watch to register a watcher on a specific property within the state and use that to detect when the state is replaced:

    https://vuex.vuejs.org/api/#watch

    Of course you'd need to be careful to structure things so that only the calls to replaceState trigger the watcher, which may get fiddly.

    A further alternative would be to patch/override the replaceState method. Replace it with your own method that calls out to the original, giving you a hook point for any extra functionality you might need.

    I've attempted to demonstrate all of the above in the example below:

    // Can't use localStorage in an SO snippet...
    const fakeStorage = {
      vuex: `{"flag": {}, "number": ${Math.random()}}`
    }
    
    const storage = {
      getItem (key) {
        return fakeStorage[key]
      },
      
      setItem (key, value) {
        fakeStorage[key] = value
      },
      
      removeItem (key) {
        delete fakeStorage[key]
      }
    }
    
    // Override replaceState with our own version
    class StoreOverride extends Vuex.Store {
      replaceState (...args) {
        super.replaceState(...args)
        console.log('replaceState called')
      }
    }
    
    const store = new StoreOverride({
      state: {
        flag: {},
        number: 0
      },
      
      plugins: [
        createPersistedState({
          rehydrated () {
            console.log('rehydrated called')
          },
          
          storage
        })
      ]
    })
    
    store.subscribe(() => {
      console.log('store.subscribe called (will not happen for replaceState)')
    })
    
    store.watch(state => state.flag, () => {
      console.log('store.watch called')
    })
    
    console.log('creating Vue instance')
    
    new Vue({
      el: '#app',
      store,
      
      methods: {
        onReplace () {
          // The property 'number' is changed so we can see something happen
          this.$store.replaceState({
            flag: {},
            number: this.$store.state.number + 1
          })
        }
      }
    })
    <script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
    <script>
    Vue.config.devtools = false
    Vue.config.productionTip = false
    </script>
    <script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vuex-persistedstate.umd.js"></script>
    
    <div id="app">
      <button @click="onReplace">Replace</button>
      <p>{{ $store.state.number }}</p>
    </div>