Search code examples
arraysvue.jsstatevuexgetter

Vuex getter for filter not updating after mutating array of state


I'm building a todo app. There is a button to toggle the checked/completed state of all todos. I'm mutating an array of the state, but the getters are not being updating. When I click toggle complete button, the database and the array state.todos update, but the $store.getters.filteredTodos does not update.

So the some todos are displayed incorrectly (some are checked and others are not when all should be checked or unchecked)

    // in getters
    filteredTodos(state) {
        if (state.filter == 'active') {
            return state.todos.filter(todo => !todo.completed);
        }

        if (state.filter == 'completed') {
            return state.todos.filter(todo => todo.completed);
        }

        return state.todos;
    },

    // in mutations
    retrieveTodos(state, todos) {
        Vue.set(state, 'todos', todos);

        // tried the following, but also didn't work
        //state.todos.splice(0, state.todos.length, ...todos);
    },

    // in actions
    toggleCompleted(context, completed) {
        axios.patch('/todos/toggle-completed', {completed})
            // response.data is the array of the updated todo items
            .then(response => context.commit('retrieveTodos', response.data))
            .catch(e => console.log(e));
    },

From several posts here in StackOverflow and in VueForum, I have seen the same solution:

        Vue.set(state, 'todos', todos);
        // or
        state.todos.splice(0, state.todos.length, ...todos);

None of them worked. The $store.state updates, but the $store.getters.filteredTodos does not.

How to solve this?

UPDATE

I'm following this tutorial series on YouTube to get started with Vue, but I'm doing somethings on my own. In the tutorial, the guy creates the Vue project for the fronend and a Laravel project for the backend. I did the same.


Solution

  • I discovered that the problem had nothing to do with getters or state. Both were being updated correctly in the three ways that I tried.

    The problem was in the Todo component. Previously, the code was something like this:

    // in the template 
            <label class="todo-action" :for="`todo-checked-${id}`">
                <input type="checkbox" v-model="completed" 
                       :id="`todo-checked-${id}`" @change="update">
            </label>
    
            <div class="todo-title">
                {{title}}
            </div>
    
    // inside script tag
        data() {
            // this allow me to access this.id, this.completed, and this.title 
            // instead of this.todo.id, this.todo.completed, and this.todo.title
            return this.todo;
        },
    

    The problem is that data was not being updated. I fixed it as follows

    // in the template I access the props from the todo item, so it is always up to date
            <label class="todo-action" :for="`todo-checked-${todo.id}`">
                <input type="checkbox" v-model="todo.completed" 
                       :id="`todo-checked-${todo.id}`" @change="update">
            </label>
    
            <div class="todo-title">
                {{todo.title}}
            </div>
    

    Yeah, it was a very simple solution