What causes the to-do item underneath a crossed-off one to also be crossed out when the to-do item above is removed?

How come the todo item below a crossed one becomes crossed when a todo-item above is deleted?

For instance:

item1 item2 item3 item4

Mark item3 as crossed out:

item1 item2 item3 X item 4

Delete item 2:

item1 item3 item4 X

What mistake did I make in my Vue code? I just started learning Vue yesterday, so my error is probably fundamental.

<div id="app">
    <todo-list>
    
    </todo-list>
</div>

<style>
    .crossed {
        text-decoration : line-through;
    }
    
    .todo:hover {
        cursor: pointer;
        font-weight: bold;
    }
</style>



<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
    
Vue.component('todo-list',{

    data: function () {
        return {
            newText: '',
            todos:[],
            todoId: 0
        }
    },
    template: `
        <div>
            <form v-on:submit.prevent="addTodo">
                <input v-model="newText">
                <button>ADD TODO</button>
            </form>
            <ul>
                <todo class="todo" v-for="(todo,index) in todos" :text="todo.text" :key="todo.todoId" @remove="removeTodo(index)"></todo>
            </ul>
        </div>
    `,
    methods: {
        addTodo: function() {
            this.todos.push({
                text: this.newText,
                done: false,
                id: this.todoId++
            });
            this.newText = '';
        },
        removeTodo: function(index) {
            this.todos.splice(index,1);
        }
    }

});

Vue.component('todo',{
    props: ['text'],
    data: function () {
        return {
            done: false
        }
    },
    template: `
        <li> 
            <span v-on:click="done = !done" :class="{crossed: done}">
                {{text}}
            </span>
            <button v-on:click="$emit('remove')">Remove</button>
        </li>
    `
}) 

new Vue({
    el: '#app'
})
    



</script>

Answer №1

It appears that you may have used :key="todo.todoId" instead of :key="todo.id", causing the third element to be replaced by the fourth due to a text property replacement. If corrected, it should function properly.

By the way, the value of todo.todoId is currently set to undefined.

Answer №2

Manipulate the done value within the child's todo component, but prioritize maintaining a single source of truth, which should reside in the parent data properties called todos.

I approached this by passing down the todo object as props to the child todo component. Upon toggling the 'done' state, I emitted an event to signal the removal operation and updated the 'done' value in the parent component accordingly.


In-depth Explanation:

Below is an illustration of your child todo component structure.

Vue.component('todo',{
    props: ['text'],
    data: function () {
        return {
            done: false
        }
    },
    template: `
        <li> 
            <span v-on:click="done = !done" :class="{crossed: done}">
                {{text}}
            </span>
            <button v-on:click="$emit('remove')">Remove</button>
        </li>
    `
}) 

The 'done' property is declared in the data section, not passed down from props.

What happens here is that you are essentially creating a new 'done' property, let's refer to it as 'childDone'. This 'childDone' only exists within this specific component and is independent of the 'done' property in the parent todos array. Hence, altering 'childDone' won't affect the 'done' state in the parent component.

Despite showing a crossed-out todo item UI indication, this visual representation corresponds to 'childDone,' not the parent 'done.' Since the parent 'done' isn't transmitted downwards, the child todo component doesn't render based on the parent's 'done' property.


To shed light on why deleting item 2 results in item 4 becoming marked as completed:

  1. todos.length = 4, rendering 4 components
    • component1:todo1:childDone=false
    • component2:todo2:childDone=false
    • component3:todo3:childDone=false
    • component4:todo4:childDone=false
  2. Marking the 3rd component as done,
    • component1:todo1:childDone=false
    • component2:todo2:childDone=false
    • component3:todo3:childDone=true
    • component4:todo4:childDone=false
  3. Deleting the 2nd todo element, leading to todos.length = 3 and subsequently rendering 3 components
    • component1:todo1:childDone=false
    • component2:todo3:childDone=false
    • component3:todo4:childDone=true
    • compoenent4(no longer rendered due to todos.length = 3)

<div id="app">
    <todo-list>
    
    </todo-list>
</div>

<style>
    .crossed {
        text-decoration : line-through;
    }
    
    .todo:hover {
        cursor: pointer;
        font-weight: bold;
    }
</style>



<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
    
Vue.component('todo-list',{

    data: function () {
        return {
            newText: '',
            todos:[],
            todoId: 0
        }
    },
    template: `
        <div>
            <form v-on:submit.prevent="addTodo">
                <input v-model="newText">
                <button>ADD TODO</button>
            </form>
            <ul>
                <todo class="todo" v-for="(todo,index) in todos" :todo="todo" :key="todo.todoId" @remove="removeTodo(index)" @done="doneTodo(index)"></todo>
            </ul>
        </div>
    `,
    methods: {
        addTodo: function() {
            this.todos.push({
                text: this.newText,
                done: false,
                id: this.todoId++
            });
            this.newText = '';
        },
        removeTodo: function(index) {
            this.todos.splice(index,1);
        },
        doneTodo: function(index) {
            this.todos[index].done = !this.todos[index].done;
        }
    }

});

Vue.component('todo',{
    props: ['todo'],
    template: `
        <li> 
            <span v-on:click="$emit('done')" :class="{crossed: todo.done}">
                {{todo.text}}
            </span>
            <button v-on:click="$emit('remove')">Remove</button>
        </li>
    `
}) 

new Vue({
    el: '#app'
})
    



</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Vue input components are failing to render properly following the integration of different projects

Currently, I am in the process of integrating an established Vue/Vuetify project into Ionic. While most aspects are functioning smoothly, I have encountered a challenge with rendering form inputs correctly - they all seem to be arranged vertically: <tem ...

Setting up Nginx and Vue.js 3 to work seamlessly with base URLs and public paths

I am currently working on a VueJS app with history mode enabled. The website URL is example.com and the base URL of my app is configured as my-app. Everything works fine when accessing the app through example.com/my-app and navigating between pages. Howeve ...

Potential dangers involved in sending HTML content through an AJAX request over a secure HTTPS connection

I am exploring the idea of dynamically loading HTML content, such as updating a Bootstrap modal dialog's contents using AJAX calls instead of refreshing the entire page. However, before I proceed, I want to understand the potential risks involved and ...

"VueJS application can now be restarted with the simple push of a

Is there a way to reload the application in VueJS when a button is pressed? I've attempted the following: this.$nextTick(() => { var self = this; self.$forceUpdate(); }); However, $forceUpdate() does not serve the purpose I need. ...

The issue with the v date picker is that it is not triggering the change event when a single

I am having some trouble detecting the change event of a date picker. It was working perfectly when the user selected multiple days from the calendar, but if they only select a single date, the change method does not get triggered. Here is my attempt: < ...

Incorporating Bootstrap 4 into NuxtJS

I'm currently integrating Bootstrap 4 (bootstrap-vue) with Nuxt and facing some challenges. I am having trouble utilizing mixins and variables in pages or components, even after including the style-resources-module. Below is a snippet of my nuxt.con ...

Obtain an HTML element using JavaScript

What is the best method to access the top-left cell of a table using JavaScript from a specified URL? For instance, in this URL: https://www.w3schools.com/jsref/dom_obj_table.asp In the 'Table Object Methods' section, I am interested in retriev ...

During my JavaScript coding session, I encountered an issue that reads: "Uncaught TypeError: $(...).popover is not a function". This

Encountering an issue while trying to set up popovers, as I am receiving this error: Uncaught TypeError: $(...).popover is not a function. Bootstrap 5 and jQuery links are included in the code, but despite that, the functionality is still not operational. ...

struggling to locate path in Node.js REST API using Express and ES6 classes

I'm currently setting up a RESTful API using Node.js, and I've opted to utilize ES6 classes for the task. This is how my app.js file appears: const express = require("express"); const morgan = require("morgan"); const bodyParser = require("body- ...

Leveraging split and map functions within JSX code

const array = ['name', 'contact number'] const Application = () => ( <div style={styles}> Unable to display Add name & contact, encountering issues with splitting the array). </div> ); I'm facing difficul ...

The issue arises when a variable inside a mat dialog text is not defined properly within an Angular application

I have utilized angular to create a dialog for gathering information and saving it on my back-end. The issue arises when attempting to send the data to the back-end using the post method, as the variable "val" remains undefined. The specific variable in q ...

Triggering successive jQuery confirm boxes once the previous one has finished processing

I am facing an issue with my API call response where I receive an array and need to open jQuery Confirm one by one for each item in the response. The problem is that they all open at once. Below is the code snippet: axios.post('/orders/ask-for-or ...

Not all CSS custom properties are functioning properly, despite being included in the HTML code and being visible in the inspector tool

I've been experimenting with implementing different themes on my website. I modified my main stylesheet to include variables and created three additional "theme" stylesheets that define these variables at the :root level. The HTML file then connects t ...

Troubleshooting the issue of a callback function not properly updating state within the componentDidMount

I am currently utilizing Next.js and have the following functions implemented: componentDidMount = () => { //Retrieves cart from storage let self = this this.updateCart(Store.getCart(), self) ... } updateCart = (cart, self) => { ...

The style was not applied from because it has a MIME type of 'text/html', which is not a supported stylesheet MIME type, and strict MIME checking is turned on

I'm currently working on creating a Google web app that relies on a Google sheet in the backend. I prefer to develop locally in VSCode since the editor in Google Apps Scripts is quite limited. Additionally, I aim to incorporate Vue3 into my project. ...

What is a superior option to converting to a promise?

Imagine I am creating a function like the one below: async function foo(axe: Axe): Promise<Sword> { // ... } This function is designed to be utilized in this manner: async function bar() { // acquire an axe somehow ... const sword = await foo ...

Adjust the positioning of an HTML element in relation to another to prevent elements from moving erratically

Currently, I am working on a website where I have implemented some javascript to achieve a 'typewriter' effect for the prominent middle-centered text displayed below. The script is designed to animate the text by typing it letter by letter in a f ...

collection of assurances and the Promise.all() method

Currently, I am dealing with an array of Promises that looks like this: let promisesArray = [ service1.load('blabla'), service2.load(), // throws an error ]; My goal is to execute all these Promises and handle any errors that occur, as ...

restore the HTML element to its initial position after being moved

I am utilizing document.getElementById('Droping1').style['-webkit-transform'] = translate(0px,'+Ground+'px)'; in order to relocate an HTML element. How can I return it to its original position for reusability without t ...

Altering the hover functionality for dynamically created class elements

I have a vision to create a unique e-commerce storefront with tile design, featuring an item thumbnail that reveals the item name upon hovering. I found inspiration from this example, but I want the item name to slide up smoothly on hover, rather than simp ...