Strange Behavior Detected in ForLoop of Simple Todo App Built with Javascript

I attempted to create my own TODO app using HTML, CSS, and JS. Everything was functioning properly, except for this strange issue:

When I add a todo item, the for loop will attach an addEventListener to it. However, when I click on some items, the event listener does not work as expected.

The Problem: If I create more than one todo item, some of them stop working (the addEventListener does not respond when clicked).

It seems that if I create 1 item: - Item 1: works fine

If I create 2 items: - Item 1: not working - Item 2: works fine

If I create 3 items: - Item 1: works fine - Item 2: not working - Item 3: works fine ...and so forth. Is there any explanation or solution on how to fix this?

HTML CODE

<div id="form">
 <p id="error">Fill The Empty !</p>
<input id="input" type="text" placeholder="Text Here!" >
  <button id="add" type="submit" onclick="addIt()">Add</button>
</div>  
<div id="listContainer">
  <ul id="list">    

  </ul>
  <p id="noItems">You Have No Items!</p>  
</div>

CSS CODE

margin: 0px;
padding: 0px;
font-family: monospace, sans-serif;
list-style: none;
font-size: 10pt;
box-sizing: border-box;
}
#form{
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
}
#error{
color: red;
display: none;
margin: 5px 0px;
}
#input{
width: 95%;
height: 40px;
text-align: center;
margin: 5px 0px;
border: 1px purple dashed;
}
#add{
height: 40px;
width: 95%;
border: 0px;
color: white;
background-color: purple;
font-weight: 900;
}
#add:active{
color: purple;
background-color: white;
}
#listContainer{
margin-top: 40px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100vw;
}
#list{
display: flex;
flex-direction: column-reverse;
justify-content: center;
align-items: center;
width: 100vw;
}
.item{
position: relative;
text-align: center;
padding: 10px;
margin: 5px;
width: 95%;
color: purple;
background-color: white;
border: 1px purple solid;
font-size: 11pt;
}
.delete{
position: absolute;
right: 0px;
top: 0px;
padding: 10px;
width: 50px;
color: white;
background-color: red;
font-size: 11pt;
font-weight: 900;
}
#noItems{
color: lightgray;
margin-top: 50px;
/*display: none;*/
}

JS CODE

let storeInput = "";

function addIt(){
/*---addIT() start---*/
  let input = document.getElementById("input");
  storeInput = input.value;
  if(storeInput == ""){
    let errorMsg = document.getElementById("error");
    errorMsg.style.display = "block";
    setTimeout(function(){errorMsg.style.display = "none";}, 2000)
  }else{
    input.value = "";
    let item = document.createElement("LI");
    item.className = "item";
    item.innerHTML = storeInput;
    let list = document.getElementById("list");
    list.appendChild(item); 
    let deleteIt = document.createElement("I");
    deleteIt.className = "delete";
    deleteIt.innerHTML = "X";
    item.appendChild(deleteIt);
  }
  let allItems = document.querySelectorAll(".item");
  for(var i = 0; i < allItems.length; i++){
    allItems[i].addEventListener("click", function(){
      if(this.style.textDecoration == "line-through"){
        this.style.textDecoration = "none";
      }else{
        this.style.textDecoration = "line-through";
      }
    })
  }
  let deleteItem = document.querySelectorAll(".delete");
  for(var j = 0; j < deleteItem.length; j++){
    deleteItem[j].addEventListener("click", function(){
      var deleteIt = this.parentElement;
      deleteIt.remove();
    })
  }
  document.querySelectorAll(".item").length;
  if(allItems.length == 0){
    document.getElementById("noItems").style.display = "block";
  }else{
    document.getElementById("noItems").style.display = "none";
  }
/*---addIT() end---*/}

If you want to test the app live: Click here

Thank you in advance.

Answer №1

The addIt() function attaches event listeners to all items and delete buttons every time a new element is added. It should only attach event listeners to the current item. View on CodePen

let storeInput = "";

function addIt(){
/*---addIT() start---*/
  let input = document.getElementById("input");
  storeInput = input.value;
  if(storeInput == ""){
    let errorMsg = document.getElementById("error");
    errorMsg.style.display = "block";
    setTimeout(function(){errorMsg.style.display = "none";}, 2000)
  }else{
    input.value = "";
    let item = document.createElement("LI");
    item.className = "item";
    item.innerHTML = storeInput;
    let list = document.getElementById("list");
    list.appendChild(item); 
    let deleteIt = document.createElement("I");
    deleteIt.className = "delete";
    deleteIt.innerHTML = "X";
    item.appendChild(deleteIt);
    item.addEventListener("click", function(){
      if(this.style.textDecoration == "line-through"){
        this.style.textDecoration = "none";
      }else{
        this.style.textDecoration = "line-through";
      }
    });
    deleteIt.addEventListener("click", function(){
      var deleteIt = this.parentElement;
      deleteIt.remove();
    })
  }
  let allItems = document.querySelectorAll(".item");


  document.querySelectorAll(".item").length;
  if(allItems.length == 0){
    document.getElementById("noItems").style.display = "block";
  }else{
    document.getElementById("noItems").style.display = "none";
  }
/*---addIT() end---*/}

Answer №2

Take a look at this code snippet as an example:

  for(var j = 0; j < deleteItem.length; j++){
    deleteItem[j].addEventListener("click", function(){
      var deleteIt = this.parentElement;
      deleteIt.remove();
    })
  }

In the above code, a loop is being run where for each iteration, a new click event is being created. The problem with this approach is that events are not being unbound. Therefore, in your current code, if you click the button, it will trigger all previous events that were triggered.

To quickly fix this issue in your code, you can add something like this:

let deleteItem = document.querySelectorAll(".delete");
for(var j = 0; j < deleteItem.length; j++){
   deleteItem[j].parentNode.replaceChild(deleteItem[j].cloneNode(true), deleteItem[j]);
})
}
deleteItem = document.querySelectorAll(".delete");
for(var j = 0; j < deleteItem.length; j++){
deleteItem[j].addEventListener("click", function(){
  var deleteIt = this.parentElement;
  deleteIt.remove();
})
}

It's important to note that the following part of the code:

  for(var j = 0; j < deleteItem.length; j++){
     deleteItem[j].parentNode.replaceChild(deleteItem[j].cloneNode(true), deleteItem[j]);
    })
  }

Will replace the current element with itself. The key difference is that cloneNode doesn't copy the event listeners, which ultimately solves the problem.

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

Exploring the advanced features of OpenOffice Draw for improved geometry analysis

Struggling with the draw:enhanced-geometry section that involves draw:enhanced-path and draw:equation. I'm working on an OOo converter but can't seem to find any concrete solutions or extensive documentation about this part. Any suggestions on ho ...

Encountering a persistent GET error in the console while working on a project involving a cocktail API

Programming: const API_URL = "www.mycocktaildb.com/api/json/v1/1/search.php?s="; const $cocktailName = $('#cocktailName'); const $instructions = $('instructions'); const $form = $('form'); const $input = $(`inpu ...

The module 'AppModule' has unexpectedly declared a value of 'Component' that was not anticipated

Recently, I've been working on transitioning my application to the new angular2 webpack rc5 setup. I have successfully generated the app module using ng cli migration. However, I am facing an issue while trying to integrate a component from my own li ...

Issues with Read More/Less functionality on a website - Troubleshooting with JavaScript, jQuery, and Bootstrap 4

I'm looking to create a simple, lightweight feature where clicking on a "read more" link will reveal a paragraph, and then clicking on a "read less" link will hide it again. My knowledge of JS and JQuery is quite limited. I'm currently utilizing ...

What is the most effective way to update specific values in mongoose without inadvertently setting other values to null?

I am trying to update values in the collection, but the only way I can get it to work is by updating all values together. If I try to update just one value, the rest of the values become null. If I exclude updating the file (image), I get an error. 1)Upda ...

Use JavaScript or jQuery to dynamically add a class to a paragraph if it is in Arabic language

If I need to style paragraphs or divs in a different language such as Arabic, I would add a specific class to apply a unique font and direction automatically. <div class="arabic-paragraph"> <p>عربي لغة عربية تغير الات ...

Is it possible to globally delay the execution of WebElement.sendKeys() in Protractor's onPrepare function?

While running protractor on a sluggish machine, I am in need of slowing down each key press and action performed. The action part has been successfully implemented, but how can I achieve the same for key presses? I have come up with a local solution which ...

What is the process for executing JavaScript in a secondary thread in V8?

In the v8 engine, JavaScript code is only able to run in the main thread. My goal is to run JavaScript code in a non-main thread so that CPU-intensive tasks cannot interrupt the CPU time of the main thread. However, I am currently at a loss on how to accom ...

Prolong the Slide Duration in Slider Pro Slideshow

I am currently utilizing Slider Pro from bqworks.com for a slideshow with automated cycling on my website. I would like to adjust the display duration of each image in the slideshow. The documentation mentions a property called "slideAnimationDuration," wh ...

Is there a way to dynamically hide specific ID elements in Javascript based on the size of the browser window?

I have spent countless hours searching for a solution to this issue without any success. The problem I am facing involves making certain elements on a webpage invisible when the browser window width is less than a specified size. The issue arises due to f ...

Why is the ajax request in JQuery initiated before the code in beforeSend is fully executed?

I have encountered an issue with my code. I am trying to get a Reload.gif to start playing on my webpage before sending an ajax request to reload the data. However, the GIF only starts playing after the success function is called. Here is the timeline of e ...

What is the best way to eliminate all padding from a bootstrap toast notification?

I'm attempting to display a bootstrap alert message as an overlay toast (so it automatically disappears and appears above other elements). Issue: I am encountering a gap at the bottom of the toast and struggling to remove it: https://jsfiddle.net/der ...

Starting a line series from the beginning of the y-axis on a bar chart using chart.js

We have a new request from the business regarding the implementation of chart.js. Take a look at the image below, which shows a combination of bar and line charts. The line chart contains only a few data points. https://i.sstatic.net/mCSlR.png Within th ...

Guide to utilizing AJAX to retrieve a value from a controller and display it in a popup

I need assistance with retrieving data from a controller and displaying it in an HTML pop-up when a button is clicked. Currently, the pop-up shows up when the button is clicked, but the data is not being loaded. I would like the values to be loaded and d ...

Utilizing Node and Express to promptly respond to the user before resuming the program's

I am accustomed to receiving a user's request, handling it, and providing the outcome in response. However, I am faced with an API endpoint that requires about 10 tasks to be completed across various databases, logging, emailing, etc. All of these ta ...

Is it possible to transform the original object while converting between different types in Typescript?

Consider having two distinct interfaces: interface InterfaceOne { myString: string, myNum: number } interface interfaceTwo extends InterfaceOne { myBool: boolean } Utilizing the TypeScript code below: let somedata: interfaceTwo = { my ...

React- The Autocomplete/Textfield component is not displaying properly because it has exceeded the maximum update depth limit

My autocomplete field is not displaying on the page even though I wrapped it with react-hook-form for form control. When I check the console, I see this error: index.js:1 Warning: Maximum update depth exceeded. This can happen when a component calls setSt ...

Utilizing jQuery to Trigger a JavaScript Function in a Separate File

Here is my question: I currently have 2 files: //File1.js function TaskA() { //do something here } //File2.js function TaskB() { //do something here } $(function() { TaskA(); }); The issue I am facing is that the TaskB function in File2.js ...

Can a POST request be made using the responseType "array buffer"?

Recently, I created an API that responds with an image. Initially, it worked perfectly fine when using the GET method, but due to security concerns, I had to switch to the POST method. However, I'm encountering issues as the POST method does not funct ...

What is the best way to create text boxes that change dynamically according to user input using PHP and JavaScript?

Is it possible to modify the code below to allow for dynamic dropdowns to appear based on a number entered by the user in a textbox? I would like the dropdowns to adjust based on the number provided by the user. $query = mysqli_query($con, "SELECT * FRO ...