Array of objects - correct way of updating an object within the Vue.js ecosystem

Viewed 11930

I'm not sure exactly where the issue lies with this, but I'm going to see if anyone can help me understand what is going wrong with my code.

I am utilising the Vuex store, to keep a track of some state that is constantly mutating. I'm doing this as follows:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    id: 0,
    contentBlocks: []
  },
  mutations: {
    addContentBlock(state, contentBlock) {
      contentBlock.id = state.id
      state.contentBlocks.push(contentBlock)
      state.id += 1
    },
    updateContentBlock(state, contentBlock) {
      state.contentBlocks[contentBlock.id] = contentBlock
    },
    removeContentBlock(state, contentBlock) {
      state.contentBlocks.splice(state.contentBlocks.indexOf(contentBlock), 1);
    }
  }
})

Creating and deleting blocks seems to work fine.

However, when updating a block - something is going wrong. The only way I can explain what is going wrong is by showing the log of the contentBlocks instance:

console.log(this.contentBlocks)

As you can see in the above screenshot, the object instances that has been updated (index 1 in the above example) is not quite correct, it doesn't seem to be an Observer object?

The failing line is:

state.contentBlocks[contentBlock.id] = contentBlock

So, I'm wondering...what should this be?

Update:

On the advise of the below answer I have the following:

updateContentBlock(state, contentBlock) {
  const index = state.contentBlocks.findIndex(block => block.id === contentBlock.id)
  Vue.set(state.contentBlocks, index, contentBlock)
},
removeContentBlock(state, contentBlock) {
  const index = state.contentBlocks.findIndex(block => block.id === contentBlock.id)
  state.contentBlocks.splice(index, 1, contentBlock)
}

in my store. However, this isn't removing contentBlocks.

I've tried changing the suggested code to:

state.contentBlocks.splice(index, 1)

But this is causing some odd behaviour. For example, I have blocks of indices 0, 1 and 2 ... I mutate to remove index 0. All looks good - I have only the contentBlocks at indices 1 and 2 left. Yet they come with the content from 0 and 1 ???

1 Answers

This is a limitation of Vue's reactivity regarding arrays.

See #2 of Why isn’t the DOM updating?

When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is syntax sugar for arr.splice(index, 1, value)

You can solve this by using Vue.set or Array.splice in your module:

import Vue from 'vue'

// find the block's index in the array
const index = state.contentBlocks.findIndex(block => block.id === contentBlock.id)

// using Vue.set
Vue.set(state.contentBlocks, index, contentBlock)

// using Array.splice
state.contentBlocks.splice(index, 1, contentBlock)
Related