Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 280
Sleepy Avatar asked Jun 20 '17 06:06

Sleepy


People also ask

Is Vuex getter cached?

Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. As of Vue 3.0, the getter's result is not cached as the computed property does.

What is strict mode in Vuex?

Strict mode runs a synchronous deep watcher on the state tree for detecting inappropriate mutations, and it can be quite expensive when you make large amount of mutations to the state. Make sure to turn it off in production to avoid the performance cost.

Does Vuex action return a promise?

Let's create a Vuex action that updates user profile. As you can see UPDATE_PROFILE action returns a Promise . In Vuex actions are asynchronous, so the only way to know if the HTTP request succeeded or failed is to resolve or reject the promise .

How do you access getters in actions Vuex?

In addition to commit, actions has default injected parameters which are dispatch , getters and rootGetters . So you can simply write; sendDataToServer({ commit, getters }, payload) to access getters. Save this answer.


1 Answers

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> 
like image 69
Vamsi Krishna Avatar answered Sep 21 '22 19:09

Vamsi Krishna