8

I've got a list of items, and i want to apply a style to the currently selected one. I'm also using Vuex to manage the state.

My list component:

const List = Vue.component('list', {
    template:
            '<template v-if="items.length > 0">' +
                '<ul class="list-group md-col-12">' +
                    '<a href="#" v-for="(item, index) in items" class="list-group-item list-group-item-action" v-bind:class="{ active: item.isActive }" v-on:click="selectItem(index);">{{ g.text }}</a>' +
                '</ul>' +
            '</template>'
    computed: {
        items: function() {
            return this.$store.state.items;
        }
    },
    methods: {
        selectItem: function (index) {
            this.$store.commit('selectItem', index);
        }
    }
});

My store:

const store = new Vuex.Store({
    state: {
        items: [],
        currentIndex: -1
    },
    mutations: {
        selectItem: function(state, index) {
            if (index === state.currentIndex) {
                return;
            }
            if (state.currentIndex > -1) {
                delete state.items[state.currentIndex].isActive;
            }
            state.currentIndex = index;
            state.items[state.currentIndex].isActive = true;
        }
    }
});

What I see, also using the Vue 'tab' in Chrome DevTools is that whenever I click on an item of the list, the "items" array is being correctly updated, but the class is not set on them.

Also, using the time-travel debugging to go through all the mutations, in that case the class is set.

Any idea why this behavior and how to fix it?

Stephan
  • 1,858
  • 2
  • 25
  • 46

2 Answers2

16

It turns out I should have read the docs more in depth. In particular Change Detection Caveats.

The solution was to change the store mutation thus:

selectItem: function(state, index) {
    if (index === state.currentIndex) {
        return;
    }
    if (state.currentIndex > -1) {
        Vue.delete(state.items[state.currentIndex], 'isActive');
    }
    state.currentIndex = index;
    Vue.set(state.items[state.currentIndex], 'isActive', true);
}

Key here is to use the Vue.delete and Vue.set functions.

Other answer that helped me https://stackoverflow.com/a/40961247/525843

tony19
  • 125,647
  • 18
  • 229
  • 307
Stephan
  • 1,858
  • 2
  • 25
  • 46
  • A bit late here, but it might help other people, so: You can also use `set` for objects this way: `this.arrayWithObjects1.forEach((item) => { this.$set(this.arrayWithObjects2, item.propertyName, 'newValue'); });` – Consta Gorgan Jan 09 '21 at 18:30
-2

Try following:

const List = Vue.component('list', {
    template:
            '<template>' +
                '<ul class="list-group md-col-12">' +
                    '<a href="#" v-for="(item, index) in items" class="list-group-item list-group-item-action" v-bind:class="{ active: item.isActive }" v-on:click="selectItem(index);">{{ g.text }}</a>' +
                '</ul>' +
            '</template>'
  • I'll try it tomorrow thanks. In the meantime could you provide more context? The only difference with my template that I see is that you removed the "v-if" on the template element. Why would that make a difference? – Stephan Dec 18 '16 at 14:39
  • I tried this and as I thought it makes no difference – Stephan Dec 19 '16 at 08:30