Matze
Matze

Reputation: 5508

How to properly chain Vuex actions, and correctly mutate the state between calls?

I have a Vuex store in my application that defines getters, actions and mutations. Now, one action requests data from an API and commits the received data to the mutation that manipulates the state. This particular action (named initConfiguration) loads some metadata and configurations that must be applied to the store before subsequent actions can be executed. In my root component I have some initialization code in the mounted lifetime-hook function as follows:

new Vue({
  el: '#app',
  store,
  render: h => h(App),
  mounted() {
    store.dispatch('initConfiguration').then(() => {
      store.dispatch('loadData')
    })
  }
})

The problem I am facing is that the state has not been changed before the next action loadData gets called, and thus this action fails miserably due to the missing/uninitialized data. Since there will be several actions that may be called eventually, I´d like to avoid to dispatch the loadData action right from the initConfiguration action (and as I learned, the invocation of actions by a mutation must be avoided).

This is my store implementation...

export default Vuex.Store({
  state: {
    metadata: {
      initialized: false,
      config: { }
    }
  },
  mutations: { 
    setConfiguration(state, config) {
      state.metadata.config = config
      state.metadata.initialized = true
    }
  },
  actions: {
    initConfiguration({commit}) {
      axios.get('configuration').then((response) => {
        commit('setConfiguration', response.data)
      })
    },
    loadData({commit, state}) {
      axios.get(state.metadata.config.tenantDataUrl) // crashes here due to undefined data
        .then((response) => { ... });
    }
  }
})

How do I chain actions correctly, and assure that the state gets updated in between?

Upvotes: 3

Views: 1546

Answers (1)

Trevor
Trevor

Reputation: 2922

You need to return your axios promise. Add a return to each axios call and your existing code will work.

You can also re-write like this

new Vue({
  el: '#app',
  store,
  render: h => h(App),
  async mounted() {
    await store.dispatch('initConfiguration')
    await store.dispatch('loadData')    
  }
})

.

export default Vuex.Store({
  state: {
    metadata: {
      initialized: false,
      config: { }
    }
  },
  mutations: { 
    setConfiguration(state, config) {
      state.metadata.config = config
      state.metadata.initialized = true
    }
  },
  actions: {
    async initConfiguration({commit}) {
      const response = await axios.get('configuration')
      commit('setConfiguration', response.data)
    },
    async loadData({commit, state}) {
      const response = await axios.get(state.metadata.config.tenantDataUrl) // crashes here due to undefined data
     // do whatever with response, or try/catch
    }
  }
})

If you need the vue template to update at each step, add this

new Vue({
  el: '#app',
  store,
  render: h => h(App),
  async mounted() {
    await store.dispatch('initConfiguration')
    await this.$nextTick() // cause vue to run getters and update computeds
    await store.dispatch('loadData')    
  }
})

Upvotes: 3

Related Questions