Sleepy
Sleepy

Reputation: 53

Vuex accessing state BEFORE async action is complete

I'm having issues where a computed getter accesses the state before it is updated, thus rendering an old state. I've already tried a few things such as merging mutations with actions and changing state to many different values but the getter is still being called before the dispatch is finished.

Problem

State is accessed before async action (api call) is complete.

Code structure

  1. Component A loads API data.
  2. User clicks 1 of the data.
  3. Component A dispatches clicked data (object) to component B.
  4. Component B loads object received.

Note

The DOM renders fine. This is a CONSOLE ERROR. Vue is always watching for DOM changes and re-renders instantly. The console however picks up everything.

Goal

Prevent component B (which is only called AFTER component) from running its computed getter method before dispatch of component A is complete.

Store.js

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios';

Vue.use(Vuex);

export const store = new Vuex.Store({

  state: {
    searchResult: {},
    selected: null,
  },

  getters: {
    searchResult: state => {
      return state.searchResult;
    },
    selected: state => {
      return state.selected;
    },
  },

  mutations:{
    search: (state, payload) => {
      state.searchResult = payload;
    },
    selected: (state, payload) => {
      state.selected = payload;
    },
  },

  actions: {
    search: ({commit}) => {
      axios.get('http://api.tvmaze.com/search/shows?q=batman')
        .then(response => {
          commit('search', response.data);
        }, error => {
          console.log(error);
        });
    },

    selected: ({commit}, payload) => {
      commit('selected', payload);
    },
  },

});

SearchResult.vue

<template>
<div>
  //looped
  <router-link to="ShowDetails" @click.native="selected(Object)"> 
      <p>{{Object}}</p>
  </router-link>
</div>
</template>

<script>
export default {
  methods: {
    selected(show){
      this.$store.dispatch('selected', show);
    },
  },
}
</script>

ShowDetails.vue

<template>
<div>
  <p>{{Object.name}}</p>
  <p>{{Object.genres}}</p>
</div>
</template>

<script>
export default {
  computed:{
    show(){
      return this.$store.getters.selected;
    },
  },
}
</script>

This image shows that the computed method "show" in file 'ShowDetails' runs before the state is updated (which happens BEFORE the "show" computed method. Then, once it is updated, you can see the 2nd console "TEST" which is now actually populated with an object, a few ms after the first console "TEST".

Question

Vuex is all about state watching and management so how can I prevent this console error?

Thanks in advance.

Upvotes: 5

Views: 12538

Answers (3)

Vamsi Krishna
Vamsi Krishna

Reputation: 31352

store.dispatch can handle Promise returned by the triggered action handler and it also returns Promise. See Composing Actions.

You can setup your selected action to return a promise like this:

selected: ({commit}, payload) => {
    return new Promise((resolve, reject) => {
        commit('selected', payload);
    });
} 

Then in your SearchResults.vue instead of using a router-link use a button and perform programmatic navigation in the success callback of your selected action's promise like this:

<template>
<div>
  //looped
  <button @click.native="selected(Object)"> 
      <p>{{Object}}</p>
  </button>
</div>
</template>

<script>
export default {
  methods: {
    selected(show){
      this.$store.dispatch('selected', show)
          .then(() => {
            this.$router.push('ShowDetails');
        });
    },
  },
}
</script> 

Upvotes: 9

Mykola Riabchenko
Mykola Riabchenko

Reputation: 628

You can try to use v-if to avoid rendering template if it is no search results

v-if="$store.getters.searchResult"

Upvotes: 4

Marek Urbanowicz
Marek Urbanowicz

Reputation: 13644

Initialize your states. As with all other Vue' data it is always better to initialize it at the start point, even with empty '' or [] but VueJS (not sure if Angular or React act the same, but I suppose similar) will behave much better having ALL OF YOUR VARIABLES initialized.

You can define initial empty value of your states in your store instance.

You will find that helpful not only here, but e.g. with forms validation as most of plugins will work ok with initialized data, but will not work properly with non-initialized data.

Hope it helps.

Upvotes: 0

Related Questions