2
votes

I use Vue with Vuex. In one case I use Ajax to get a presentation value. Somewhere on the way, probably in computed it's no longer reactive.

In my component:

props: [ 'x', 'y' ],
template: `
  <div class="presentation">
    {{ presentation }}
  </div>
`,
computed: {
  presentation() {
    return this.$store.getters.presentation({ x: this.x, y: this.y });
  }
}

Vuex store:

const store = new Vuex.Store({
  state: {
    table: {
      data: []
    }
  },
...

Vuex actions:

I call an url with ajax and return a promise. I also commit a mutation.

actions: {
  save: (context) => {
    let uri = 'some/uri';
    let params = {};
    let value = 'A value';

    return axios
    .post(uri, params)
    .then((response) => {
      context.commit('setPresentation', value);
    })
    .catch((error) => {
      console.log('error');
    })
    .finally(() => {});
  },
},

Vuex mutations:

mutations: {
  setPresentation: (state, value) => {
    let pos = state.table.pos;
    state.table.data[pos.y][pos.x].presentation = value;
  },
}

Vuex getters:

getters: {
  presentation: (state) => (pos) => {
    return state.table.data[pos.y][pos.x].presentation;
  }
},

I've already make sure of the following:

  • I set up the table.data state to a default value to make it reactive
  • Using a getter to get the data
  • Using an action for the ajax call
  • Call a mutation with a commit in the action

Notes:

  • The ajax call needs to be in an action and not in created, because I'm going to use presentation from more than one component.
  • I prefer a solution which does not need external Vue plugins.

Question(s)

  • What did I miss?
  • How can I solve it in the best way?
3
How are you populating the entries of the table.data array? Are you doing so via push() or are you just assigning to the indices like table.data[y] = []? - Decade Moon
@DecadeMoon I assigned them with [ ] and then loop the indexes in like [ i ] where i is an integer. - Jens Törnell

3 Answers

3
votes

You need to use Vue.set instead of state.table.data[pos.y][pos.x].presentation = value;

See https://vuejs.org/v2/guide/list.html#Caveats for details

Try to update your mutation with the following code:

if (!state.table.data[pos.y]) {
  Vue.set(state.table.data, pos.y, [])
}
Vue.set(state.table.data[pos.y], pos.x, { presentation: value })

A word from me, the OP (Original poster):

Why it failed the first times was that I only set the last part { presentation: value } with Vue.set as I already has pos.y and pos.x set in another ajax call.

For Vue to be fully aware if the change I needed to set everything that has not already been set in state, with Vue.set. So I needed use Vue.set to set pos.y and pos.x as well.

Also see another excellent answer below.

Vue cannot detect changes to an array when you directly set an item with the index

2
votes

Your mutation is OK; there's no issues there. Vue will detect the assignment to the presentation property of the object just fine as long as the object is being observed by Vue.

In most cases Vue will automatically observe objects, but there are some quirks (especially with arrays) that you need to be aware of.

Vue cannot detect changes to an array when you directly set an item with the index (docs).

I assume you are populating your arrays in the following manner:

for (let y = 0; y < Y_MAX; y++) {
  // This is bad! Vue cannot detect this change
  state.table.data[y] = []

  for (let x = 0; x < X_MAX; x++) {
    // Same as above, Vue cannot detect this change. As a consequence,
    // the object you are assigning to the array will not be observed
    // by Vue! So if you were to mutate the presentation property
    // of this object, Vue won't know about it.
    state.table.data[y][x] = { presentation: '...' }
  }
}

So to fix your problem you just need to make sure you are not mutating arrays in the following way:

array[index] = whatever

You need to do this instead:

Vue.set(array, index, whatever)

Alternatively, you can build the array first and then assign it to state.table.data last; Vue will detect the assignment and then recursively observe the array and everything contained within it.

const data = []

for (let y = 0; y < Y_MAX; y++) {
  data[y] = []

  for (let x = 0; x < X_MAX; x++) {
    data[y][x] = { presentation: '...' }
  }
}

// After this assignment, data (and all objects contained within it)
// will be observable
state.table.data = data
0
votes

It looks like your props is an array. Are you sure this.x and this.y return the correct values inside your presentation method?