user10261970
user10261970

Reputation:

Issue with computed state and mutated state with Vue.js

I'm seeing an issue with binded components set from Vuex state management.

I have a state store 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) {
      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)
      Vue.delete(state.contentBlocks, index)
    }
  }
})

I then loop over state.contentBlocks in my baseComponent.vue as follows:

<v-container pa-0 v-for="(contentBlock, index) in contentBlocks" v-bind:key="contentBlock.index">
  <component v-bind:is="contentBlock.blockComponent" v-bind:contentBlock="contentBlock"></component>
</v-container>

I then mutate state.contentBlocks by adding, updating and deleting content blocks. (I add blocks from the base component, deleting and updating from the child component that is bound to <component></component>.

I get the mutated state.contentBlocks to BaseComponent.vue using within BaseComponent.vue:

computed: {
    contentBlocks: () => store.state.contentBlocks,
}

When updating I see the following in console:

enter image description here

All is looking good. I then go to delete the block at index 1, which "works" on the state level:

enter image description here

But the DOM is not playing ball:

enter image description here

(!!) In the above, the indices of 0 and 2 are correct as the need to be, but the content in index 2 is that of the deleted index 1 (!!?) Help! :D

Upvotes: 2

Views: 72

Answers (1)

Brian Lee
Brian Lee

Reputation: 18187

In baseComponent.vue, use a computed property to loop over the content blocks rather than the state of the vuex store:

<v-container pa-0 v-for="(contentBlock, index) in blocks" v-bind:key="contentBlock.index">
  <component v-bind:is="contentBlock.blockComponent" v-bind:contentBlock="contentBlock"></component>
</v-container>

computed: {
  blocks () {
    return this.$store.getters['contentBlocks']
  }
}

You'll need to add the corresponding getter to the store as well:

export default new Vuex.Store({
  state: {
    id: 0,
    contentBlocks: []
  },
  getters: {
    contentBlocks: (state) => state.contentBlocks 
  }
  mutations: {
    addContentBlock(state, contentBlock) {
      contentBlock.id = state.id
      state.contentBlocks.push(contentBlock)
      state.id += 1
    },
    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)
      Vue.delete(state.contentBlocks, index)
    }
  }
})

Unrelated, but you can simply the removeContentBlock mutation to:

state.contentBlocks = state.contentBlocks.filter({id} => id !== contentBlock.id)

update

Add a key on the component elements:

<v-container pa-0 v-for="(contentBlock, index) in blocks" v-bind:key="contentBlock.index">
  <component v-bind:is="contentBlock.blockComponent" v-bind:contentBlock="contentBlock" :key="contentBlock.id"></component>
</v-container>

Upvotes: 1

Related Questions