Vuex store not updated when called from promise resolution

Viewed 1804

I have an array that is bound to a vuex data store and exposed as a computed property through the mapGetters helper. This array is called items, and I call a REST API to update it in the created() hook of the component. The vuex action I have for this returns a promise in which the API is accessed and the mutator called (which updates the items array) before resolving. My understanding of promises tells me that my then calls should safely occur after the asynchronous action completes, yet if I try to access items from the promise resolution, it is empty despite the array certainly being populated by the API call. Why isn't this working how I expect?

The code is all over but here are the relevant parts

Component:

  computed: {
    ...mapGetters({
      items: 'allHistoryItems'
    }),
// ...
  created () {
    this.$store.dispatch('fetchList').then(console.log(this.items))
  }

Action:

  fetchList: ({ commit }) => {
    return new Promise((resolve, reject) => {
      fetchList().then(response => {
        commit(types.FETCH_LIST, response.data)
        resolve(response)
      })
    })
  }

I can access the API response from the component, but as mentioned, items is empty. Does the reactivity not kick in until after the promise resolves?

2 Answers

In short

created () {
    this.$store.dispatch('fetchList').then(console.log(this.items))
}

When created is called, it executes:

this.$store.dispatch('fetchList')

followed immediately by

console.log(this.items)

No waiting for anything, then, when this.$store.dispatch('fetchList') resolves, .then is called like .then(undefined) ... because console.log(this.items) returns undefined

change this to

created () {
    this.$store.dispatch('fetchList').then(() => console.log(this.items));
}

As a bonus - removing the promise constructor anti-pattern:

fetchList: ({ commit }) => fetchList() 
    .then(response => {
        commit(types.FETCH_LIST, response.data);
        return response;
    }
);

I think your new Promise construction is simply redundant. Inspec this simple example if it is doing wha you want:

var store = new Vuex.Store({
  state: {
    hero: []
  },
  mutations: {
    updateHero (state, payload) {

      state.hero = payload
    }
  },
  actions: {
    async loadHero ({commit}, payload) {
      var response = await fetch('https://swapi.co/api/people/1/')
      commit('updateHero', await response.json())
    }
  }
})

new Vue ({
  el: '#app',
  store,
  computed: {
    hero () {
      return this.$store.state.hero
    }
  },
  methods: Vuex.mapActions(['loadHero'])
})
[v-cloak] {
  display: none;
}
<div id="app">
  Hero name is: <span v-cloak>{{ hero.name }}</span><br>
  <button @click="loadHero">Load hero personal data</button>
</div>

<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

Related