Can a Vuex module watch another Vuex module?

Lucas picture Lucas · Apr 20, 2018 · Viewed 7.7k times · Source

Can Vuex modules watch the state of other modules, and trigger actions consequently?

For example, let's consider the following case:

store.js

import time from './store/time' ;
import position from './store/position' ;

const store = new Vuex.Store
(
    {
        modules:
        {
            time,
            position
        }    
    }
) ;

store/time.js

export default
{

    namespaced: true,

    state:
    {
        time: new Date()
    },

    getters:
    {
        time: (aState) => aState.time
    },

    mutations:
    {

        setTime (aState, aTime)
        {
            aState.time = aTime ;
        }

    }

} ;

store/position.js

export default
{

    namespaced: true,

    state:
    {
        positions: {}
    },

    getters:
    {
        positions: aState => aState.positions
    },

    mutations:
    {

        setPositions (aState, aPositionArray)
        {
            aState.positions = aPositionArray ;
        }

    },

    actions:
    {

        fetchPositionsAtTime ({ dispatch, commit, rootGetters }, { time })
        {
            // Fetch positions at given time from the server
            // commit('setPositions', ...)
        }

    }

} ;

Ideally, I would like the position module to watch the time module, and re-fetch the positions (i.e. trigger fetchPositionsAtTime) as soon as the time state changes.

Of course, I could add a dispatch to the setTime mutation to trigger a position module action, but I believe going the other way around (i.e. watching) would be more elegant (as many more modules may require time).

Any solution to this? (without using Vue Component of course, that is the whole point)

Answer

thanksd picture thanksd · Apr 20, 2018

You can use the store instance's watch method to watch the time state and then dispatch the fetchPositionsAtTime action whenever that time value changes:

store.watch((state) => state.time.time, (val) => {
  store.dispatch('position/fetchPositionsAtTime', { time: val })
});

If you don't want to watch the state directly you can also watch a getter like so:

store.watch((state, getters) => getters['time/time'], (val) => {
  store.dispatch('position/fetchPositionsAtTime', { time: val })
});

Here's a link to the api documentation for Vuex.Store instances.


Here's an example:

const time = {
  namespaced: true,
  state: {
    time: new Date()
  },
  getters: {
    time: (aState) => aState.time
  },
  mutations: {
    setTime (aState, aTime) {
      aState.time = aTime ;
    }
  }
};

const position = {
  namespaced: true,
  state: {
    positions: {}
  },
  getters: {
    positions: aState => aState.positions
  },
  mutations: {
    setPositions (aState, aPositionArray) {
      aState.positions = aPositionArray ;
    }
  },
  actions: {
    fetchPositionsAtTime ({ dispatch, commit, rootGetters }, { time }) {
      let value = rootGetters['position/positions'].value || 0;
      commit('setPositions', { value: ++value });
    }
  }
};

const store = new Vuex.Store({
  modules: { time, position }    
});

store.watch((state) => state.time.time, (val) => {
  store.dispatch('position/fetchPositionsAtTime', { time: val })
});

new Vue({
  el: '#app',
  store,
  methods: {
    setTime() {
      this.$store.commit('time/setTime', new Date());
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<div id="app">
  time: {{ $store.getters['time/time'] }}
  <br>
  position: {{ $store.getters['position/positions'] }}
  <br>
  <button @click="setTime">Change time</button>
</div>