4
votes

I think I have a misunderstanding of the work with vue + vuex.

Given the following scenario.

I have a vuex state which contains a filter for a list. Each filter item in the could be marked as selected. BUT - the vuex state should only modified through a modifier when an apply button was clicked, so that other components could react to the change. For example the list would reload data depending on the selected filters. When the state updates, the component should be updated.

I have created a pen

https://codepen.io/anon/pen/OzEJWP?editors=1010

https://jsfiddle.net/rxqu7ame/

Vue.component('filter-item', {
template: ` <div>
            <!--{{item.color}} ({{item.selected}}) <button @click="item.selected = !item.selected">toggle</button>-->
            {{item.color}} ({{isSelected}}) <button @click="isSelected = !isSelected">toggle</button>
          </div>`,
data: function () {
return {
  isSelected: this.item.selected
}
},
computed: {
/*isSelected : function(){
  return this.item.selected;
}*/
},

props: ['item']
});   

I ran into different problems.

  1. When I toggle the ‚selected‘ property directly within the „filter-item" template, the vuex state would also updated.
  2. So I tried to initialize a data property with the state. Now only the data variable ‚isSelected‘ would be updated. When I press the ‚apply‘ button, the vuex state would be updated (later I would use a mutation) . So far, so good. But now, the ‚isSelected‘ property would not automatically updated when the state changed.
  3. If I use a computed property the „isSelected“ could not be change in the click event.

What is the right way to archieve the given scenario?

Many Thanks!

2
Could you provide codesandbox.io or jsfiddle sample ?Rafał Warzycha
Sure, i edited the post. Thanks.Chrizzz

2 Answers

0
votes

Updated codepen: https://codepen.io/guanzo/pen/xpzrMm?editors=0010

I've ran into this sort of situation before, and found it easiest to add a "temporary" variable next to the "actual" variable in the store. The "actual" variable is selected, and the "temporary" variable is tempSelected". tempSelected will be updated when the user toggles a color, and selected will be updated when you "apply to store".

Here's what the default store state looks like

filter: [
      {
        color: 'red',
        selected: false,
        tempSelected: false,
      },
      {
        color: 'green',
        selected: false,
        tempSelected: false,
      }      
    ]

Here's how to toggle a single color through a mutation. You shouldn't directly update the store through references, always do it through mutations. If you're confused about the computed get/set syntax, look here: https://vuejs.org/v2/guide/computed.html#Computed-Setter

Vue.component('filter-item', {
  template: ` <div>
                {{item.color}} ({{item.tempSelected}}) 
<button @click="isSelected = !isSelected">toggle</button>
              </div>`,
  computed: {
     isSelected:{
       get(){
         var filter = this.$store.state.filter.find(filter=>filter.color === this.item.color)
         return filter.tempSelected
       },
       set(value){
         this.$store.commit('updateSelected',{
           color: this.item.color,
           selected: value
         })
       }
     }
  },

Here's the store mutation to update a single color.

updateSelected(state,payload){
  var filter = state.filter.find(filter=>filter.color===payload.color)
  filter.tempSelected = payload.selected
}

Now you have the temp variable storing the user input. To "apply to store", you simply need to set the value of the actual variable, to the value of the temp variable

updateFilters(state){
      state.updates++;
      state.filter.forEach(filter=>{
          filter.selected = filter.tempSelected
      })
    },

To toggle all values, just flip the current selected value. But be sure to keep the tempSelected variable in sync.

toggleFilters(state){
      state.updates++;
      state.filter.forEach(filter=>{
          filter.selected = !filter.selected
          filter.tempSelected = filter.selected
      })
    },

It's now trivial to implement a "cancel changes" features. If a user wishes to undo all their changes, you can set tempSelected to the value of selected, effectively restoring the state to the last known value.

0
votes

1) For example, you can add computed property to filter-item and watcher for this property. So, filter-item component will know if vuex changed, and change isSelected property:

data: function () {
  return {
    isSelected: this.$store.state.filter[this.index].selected
  }
},
computed: {
  storeSelected: function(){
    return this.$store.state.filter[this.index].selected;
  },
  color: function(){
    return this.$store.state.filter[this.index].color;
  }
},
watch: {
  storeSelected: function(){
    this.isSelected = this.$store.state.filter[this.index].selected;
  }
},
props: ['index']

codepen: https://codepen.io/aircrisp/pen/MrXEgd?editors=0010

2)

The only way to actually change state in a Vuex store is by committing a mutation. https://vuex.vuejs.org/en/mutations.html

So, if you use mutation to change vuex state - you can subscribe to mutation in filter-item component, for example, after component mounted:

mounted: function(){
  this.$store.subscribe((mutation, state) => {
     if(mutation === 'changeFilterItem'){
       this.isSelected = state.filter[this.index].selected;
     }
  })
 },

codepen: https://codepen.io/aircrisp/pen/MrXEgd?editors=0010 (commented code)