Invert boolean on click with v-for?

Viewed 2593

Beginner to JS and VueCLI so I'll try to explain as best as I can. I'm using Express as my back-end.

I'm trying to change the boolean in my array of objects on click. I'm able to accomplish that but when I click on a different list item in my v-for loop it's flipping the boolean in all other indexes of my array. Here's my code:

Express: /routes:

        // fake data store
        const tasks = [
           { id: 1, task: 't1', completed: false},
           { id: 2, task: 't2', completed: false},
           { id: 3, task: 't3', completed: false}
        ];

        /**
        * GET handler for /tasks route
        * @returns {Array.<{id: Number, task: String, completed: Boolean}>} array of task objects 
        */
        router.get('/', (req, res) => {
           res.send(tasks);
        });

Webapp:

        /**
        * GET /tasks
        * @returns Promise => {Array.<{id: Number, task: String, completed: Boolean}>} array of task objects
        */
        export function getTasks() {
           return request('tasks');
        }

and now my Vue component:

        <template>
        <div id="tasks">
            <h2>Movies to Add</h2>
            <ul class="todo-list">
            <li v-for='task in tasks' :id="task.id" v-on:click="completeMovie($event)" :key='task.id' class="todo-list__li">
                <input class="todo-list__input" type="checkbox" :name='task.task' :id="task.task">
                <div class="todo-list__checkbox">
                <span class="todo-list__checkbox-inner"><i></i></span>
                </div>
                <label :for='task.task'>{{ task.task }}</label>
            </li>
            </ul>
        </div>
        </template>

        <script>
        import {getTasks} from './../../services/tasks';

        export default {
        name: 'TaskList',
        data: function() {
            return {
            tasks: []
            };
        },
        created: function() {
            getTasks()
            .then(res => this.tasks = res);
        },
        methods: {
            completeMovie: function (event) {
            var taskId = event.currentTarget.id -1;

            getTasks()
                .then((res) => {
                this.tasks = res;
                res[taskId].completed = !res[taskId].completed;
                });
            }
        }
        }
        </script>

So when I click on my first list item it changes the Task: t1 to True but if I click on the second list item it changes t1 back to False and t2 to True. I'm not sure exactly what I'm doing wrong. I'm not even sure this is the best way to do this. My main issue is I'm not sure why it's happening.

Any help is much appreciated!

3 Answers

You're probably over-complicating this.

All you need is

<li v-for="task in tasks" :key="task.id" 
    @click="task.completed = !task.completed" 
    class="todo-list__li">

Demo ~ https://jsfiddle.net/xy1q0auL/2/

There's no (obvious) need to re-fetch the tasks every time you click on one. This is why your previous changes are reset; it's because you overwrite all the data with the unmodified values from getTasks().

When completeMovie() is called, you send an HTTP request to your server that is gonna send you back unaltered task list. I don't understand clearly what's you're trying to achieve but your callback in the promise has no sens. In the callback you reaffect the "tasks" list in your vue :

enter image description here

In this case, here is the event timeline you have to do :

  • When client page is loaded, Vue Initialized, the vue component calls your webservice to get the task list then print it.
  • Then when I click on a task, you have to make a another HTTP call to your webservice (on another route), that will update the task list and will return it.
  • Then in the call of your vue component you reaffect the new task list.

You can make its simply:

 <li v-for='task in tasks' :id="task.id" v-on:click="completeMovie(task.id)" :key='task.id' class="todo-list__li">
            <input class="todo-list__input" type="checkbox" :name='task.task' :id="task.task">
            <div class="todo-list__checkbox">
            <span class="todo-list__checkbox-inner"><i></i></span>
            </div>
            <label :for='task.task'>{{ task.task }}</label>
        </li>

Ans for the method

completeMovie: function ($taskId) {
        this.tasks[$taskId].completed = !this.tasks[$taskId].completed;

        }
    }
Related