3

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?

ssb
  • 1,352
  • 3
  • 18
  • 40
  • [Avoid the promise constructor anti-pattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it) - having said that, how does the "Action" change items? I can't see the connection – Jaromanda X Nov 06 '17 at 10:48
  • ahhh `.then(console.log(this.items))` ... should be `.then(() => console.log(this.items))` ... .then needs a function as an argument, not the result of calling console.log (which is undefined by the way) – Jaromanda X Nov 06 '17 at 10:49
  • I'll check the article. Items is mapped to a getter that manipulates and returns the list fetched by the API. – ssb Nov 06 '17 at 10:50
  • ignore my first comment, the error you made is in my second comment – Jaromanda X Nov 06 '17 at 10:52
  • Go figure it's something as basic as that. I'll test it later but make it into an answer and I'll accept it. – ssb Nov 06 '17 at 10:54

2 Answers2

2

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;
    }
);
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
1

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>