[vuex] module is not reactive when module state is initially empty

Hello,

I created store module which initially is empty and get populated after an action is called.

testModule:

const testModule = {
    	namespaced: true,
    	state: {},
        mutations: {
      	setItem(state, item) {
           // Not reactive
           state[item.id] = item.name;
          
             // Reactive
            //Vue.set(state, item.id, item.name);
        }
      },
      actions: {
      	fetchItem({commit}, id) {
        	const item = {
          	id,
            name: 'test',
          };
          
          commit('setItem', item);
        }
      },
    };


    const store = new Vuex.Store({
      modules: {
      	test: testModule,
      },
    })

“test” component dispatch an action, on created, to store an entry to the state with a specific key.

Vue.component('test', {
  template: '<div>name: {{ item }}</div>',
  data() {
  	return { id: 3 }
  },
  created () {
    this.$store.dispatch('test/fetchItem', this.id);
    
    setTimeout(() => { 
    this.$store.commit('test/setItem', {
    	id: this.id, 
      name: 'reacted'
    }); 
    }, 5000);
  },
  computed: {
  	item: function () {
    	console.log('Evaluated');
    	return this.$store.state.test[this.id];
    }
  }
});

But the item is not reactive and does not change after the second commit.

Using Vue.set on the mutation seems to solve the reactivity issue but the getter is evaluated everytime the state changes and not when the specified key changes.

Vue.set(state, item.id, item.name);

A simplified example can be viewed here: https://jsfiddle.net/atoumpas/2o43qmf7/68/

Thank you for any help.

Hello @antoumpas,

Vue components data and Vuex module states, needs to be predefined to be reactive. Definitely you need to use Vue.set or $set to add some property which is not reactive.

When you update any property in created it will be set in that life-cycle and when the component is mounted it have that initialized value. however to get re-render you need the properties to be reactive.

Also When you do

Vue.set(obj,‘key’,value)

it trigger every getters or computed (basically all watchers) which use that obj you are setting.

1 Like

Thanks for the quick reply.

Let’s say i have a computed property:

item() {
  console.log('Evaluated');
  return this.$store.state['2'];
}

using

Vue.set(state, id, content); // where id = ‘3’; for example

on the mutation will trigger the computed property even if the value of the key used on the computed property didn’t change.

Is there a way to avoid that unnecessary call of the getter?

It happens only if mutation sets the new key and it is right behaviour, because Vue doesn’t track non-existing keys: no property - no reactive getter - no tracking.
for example:

item() {
  // if 'some' is not in state, vue cannot track that this getter touches 'some'   
  // thus, if any new key is set, this getter (and any others that depend on state) should be recomputed
  var some = this.$store.state['some']; 
  return  some !== undefined ? some : 'not found' ;
}
1 Like

yes, every mutation setting up the new key using Vue.set() will run the watchers

So, You can do a logic here as

mutations: {
  	setItem(state, item) {
      if(state[item.id]){
        // will be reactive as new key already added with vue.set but only run relevent watchers
      	state[item.id] = item.name;
      }else{
        // set up any new Reactive property and will run all watchers..
      	Vue.set(state, item.id, item.name);
      }  	 
    }
  },
1 Like