0
votes

I've read multiple similar questions about this here and elsewhere, but I can't figure it out.

I have a form with mapGetters and input values that should update based on Vuex state:

    ...mapGetters({
      show: "getShow"
    }),

sample form input (I'm using Bootstrap Vue):

      <b-form-input
        id="runtime"
        name="runtime"
        type="text"
        size="sm"
        v-model="show.runtime"
        placeholder="Runtime"
      ></b-form-input>

Then I have this method on the form component:

    async searchOnDB() {
      var showId = this.show.showId;
      if (!showId) {
        alert("Please enter a showId");
        return;
      }
      try {
        await this.$store.dispatch("searchShowOnDB", showId);
      } catch (ex) {
        console.log(ex);
        alert("error searching on DB");
      }
    },

and this action on the store:

    async searchShowOnDB({ commit, rootState }, showId) {
      var response = await SearchAPI.searchShowOnDB(showId);
      var show = {
        show_start: response.data.data.first_aired,
        runtime: response.data.data.runtime,
        description: response.data.data.overview
      };
      //I'm updating the object since it could already contain something
      var new_show = Object.assign(rootState.shows.show, show);
      commit("setShow", new_show);
    }

mutation:

    setShow(state, show) {
      Vue.set(state, "show", show);
    }

searchAPI:

export default {
    searchShowOnDB: function (showId) {
        return axios.get('/search/?id=' + showId);
    },
}

Everything works, the API call is executed, I can even see the Vuex updated state in Vue Devtools, but the form is not updated. As soon as I write something in an input field or hit commit in Vue Devtools, the form fields show_start, runtime, description all get updated.

Also, this works correctly and updates everything:

    async searchShowOnDB({ commit, rootState }, showId) {
      var show = {
        show_start: "2010-03-12",
        runtime: 60,
        description: "something"
      };
      //I'm updating the object since it could already contain something
      var new_show = Object.assign(rootState.shows.show, show);
      commit("setShow", new_show);
    }

I don't know what else to do, I tried by resolving Promises explicitly, remove async/await and use axios.get(...).then(...), moving stuff around... nothing seems to work.

2
Maybe try doing a reproduction of the issue but on a smaller scale and share with us please :) Via CodeSandbox or JSFiddleShailen Naidoo
I reproduced it here: codesandbox.io/s/brave-sun-yjl36 - new window: yjl36.csb.app - just enter a name and hit search. Nothing will change. then as soon as you edit the input, what I need will appear.koichirose

2 Answers

1
votes

On line 15 of your /modules/search.js you're using Object.assign() on rootState.search.show. This mutates the search prop of the state (which is wrong, btw, you should only mutate inside mutations!). Read below why.

And then you're attempting to trigger the mutation. But, guess what? Vue sees it's the same value, so no component is notified, because there was no change. This is why you should never mutate outside of mutations!

So, instead of assigning the value to the state in your action, just commit the new show (replace lines 15-16 with:

 commit('setShow', show);

See it here: https://codesandbox.io/s/sharp-hooks-kplp7?file=/src/modules/search.js

This will completely replace state.show with show. If you only want to merge the response into current state.show (to keep some custom stuff you added to current show), you could spread the contents of state.show and overwrite with contents of show:

commit("setShow", { ...rootState.search.show, ...show });

Also note you don't need Vue.set() in your mutation. You have the state in the first parameter of any mutation and that's the state of the current module. So just assign state.show = show.

And one last note: when your vuex gets bigger, you might want to namespace your modules, to avoid any name clashes.

0
votes

All props of objects in a state that is used in templates must exist or you should call Vue.set for such properties.

  state: {
    show: {
      runtime: null // <- add this line
    }
  },

You call Vue.set for the whole object but it already exist in the state and you do not replace it by a new one you just replace props. In your case you have an empty object and add the 'runtime' prop it it using Object.assign. Also all manipulations with state should be done in mutations:

      var new_show = {
        runtime: response.data.url
      };
      commit("setShow", new_show);
...
  mutations: {
    setShow(state, new_show) {
      Object.assign(state.show, new_show)
    }
  },