Parametized getter in Vuex - trigger udpate

Viewed 975

My Vuex store has a collection of data, say 1,000 records. There is a getter with a parameter, getItem, taking an ID and returning the correct record.

I need components accessing that getter to know when the data is ready (when the asynchronous fetching of all the records is done).

However since it's a parametized getter, Vue isn't watching the state it depends on to know when to update it. What should I do?

I keep wanting to revert to a BehaviorSubject pattern I used in Angular a lot, but Vuex + rxJS seems heavy for this, right?

I feel I need to somehow emit a trigger for the getter to recalculate.

store.js

import Vue from 'vue'
import Vuex from 'vuex'
import VueResource from 'vue-resource'

Vue.use(VueResource);

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    numberOfPosts : -1,
    posts : {}, //dictionary keyed on slug
    postsLoaded : false,
  },
  getters : {
    postsLoaded : function(state){
      return state.postsLoaded;
    },
    totalPosts : function(state){
      return state.numberOfPosts;
    },
    post: function( state ){
      return function(slug){
        if( state.posts.hasOwnProperty( slug ) ){
          return state.posts.slug;
        }else{
          return null;
        }
      }
    }
  },
  mutations: {
    storePosts : function(state, payload){
      state.numberOfPosts = payload.length;
      for( var post of payload ){
        state.posts[ post.slug ] = post;
      }
      state.postsLoaded = true;
    }
  },
  actions: {
    fetchPosts(context) {
      return new Promise((resolve) => {
        Vue.http.get(' {url redacted} ').then((response) => {
          context.commit('storePosts', response.body);
          resolve();
        });
      });
    }
  }
})

Post.vue

 <template>
      <div class="post">
        <h1>This is a post page for {{ $route.params.slug }}</h1>
        <div v-if="!postsLoaded">LOADING</div>
        <div v-if="postNotFound">No matching post was found.</div>
        <div v-if="postsLoaded && !postNotFound" class="post-area">
          {{ this.postData.title.rendered }}
        </div>
      </div>
    </template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'post',
  data : function(){
    return {
      loading : true,
      postNotFound : false,
      postData : null
    }
  },
  mounted : function(){
    this.postData = this.post( this.$route.params.slug );
    if ( this.postData == null ){
      this.postNotFound = true;
    }
  },
  computed : mapGetters([
    'postsLoaded',
    'post'
  ])
}
</script>

As it stands, it shows the "post not found" message because when it accesses the getter, the data isn't ready yet. If a post isn't found, I need to distinguish between (a) the data is loaded and there isn't a post that matches, and (b) the data isn't loaded so wait

3 Answers

I suspect the problem lies with how your are setting the posts array in your storePosts mutation, specifially this line:

state.posts[ post.slug ] = post

VueJs can't track that operation so has no way of knowing that the array has updated, thus your getter is not updated.

Instead your need to use Vue set like this:

Vue.set(state.posts, post.slug, post)

For more info see Change Detection Caveats documentation

Sorry I can't use code to illustrate my idea as there isn't a running code snippet for now. I think what you need to do is that:

  1. Access the vuex store using mapGetters in computed property, which you already did in Post.vue.
  2. Watch the mapped getters property inside your component, in your case there would be a watcher function about postsLanded or post, whatever you care about its value changes. You may need deep or immediate property as well, check API.
  3. Trigger mutations to the vuex store through actions, and thus would change the store's value which your getters will get.
  4. After the watched property value changes, the corresponding watch function would be fired with old and new value and you can complete your logic there.

code sample of mark's answer

computed: {
        ...mapGetters([
            'customerData',
        ])
},
methods: {
    ...mapActions(['customerGetRecords']),
},
created() {
    this.customerGetRecords({
        url: this.currentData
    });
Related