-1

Javascript event Listeners are not working on updating the DOM.

**HTML CODE: **



    <div class="task-list">
        
    </div>

**JAVASCRIPT CODE: **

function showList(num) {
    const taskBox = document.querySelector('.task-list'); 
    let listHtml = "";
            
    for(let i =1;i<=num;i++){
        listHtml +=  `
            <li class="list-item">Hello ${i}</li>
        `;
    }

    taskBox.innerHTML = listHtml; 
}

showList(5);


const listItem = document.querySelectorAll('.list-item'); 
listItem.forEach((item) => {
    item.addEventListener('click', (e) => {
        console.log(e.target.innerText);
        showList(4);
    }); 
}); 

With this code event Listener just working once. After that it is not working and not even showing error. So why this is happening. How to udpation of the DOM affecting eventlistener exactly.

I have faced this problem multiple times, I solved this by using onclick() function on each elmeent, but i never got solution of why it is not working in this way.

RAJ PAWAR
  • 1
  • 1

3 Answers3

1

The reason is that after you invoke showList() again,you have replaced the old elements with new elements,and they do not have event binds,you need to add click event again

const listItem = document.querySelectorAll('.list-item'); 
listItem.forEach((item) => {
    item.addEventListener('click', (e) => {
        console.log(e.target.innerText);
        showList(4);// after create new element,need to add click event again
    }); 
}); 

function showList(num) {
    const taskBox = document.querySelector('.task-list'); 
    let listHtml = "";
            
    for(let i =1;i<=num;i++){
        listHtml += `<li class="list-item">Hello ${i}</li>`;
    }

    taskBox.innerHTML = listHtml;
    addClickEvent(); // add click event again
}

function addClickEvent(){
  const listItem = document.querySelectorAll('.list-item'); 
  listItem.forEach((item) => {
      item.addEventListener('click', (e) => {
          console.log(e.target.innerText);
          showList(4);
      }); 
  });
}

showList(5);
<div class="task-list">
        
</div>
flyingfox
  • 13,414
  • 3
  • 24
  • 39
  • 1
    "*you need to add click event again*" or use the better solution: event delegation – VLAZ Nov 11 '22 at 07:28
  • @VLAZ If OP use `jQuery`,then `on()` is a better choice,but I do not found a solution to dynamiclly bind with pure `javascript` – flyingfox Nov 11 '22 at 07:29
  • 1
    How is jQuery relevant? It's just using the DOM event model with very slight enhancements. It doesn't *change* how the events work. All of them bubble up the tree. Event delegation is not a jQuery concept [What is DOM Event delegation?](https://stackoverflow.com/q/1687296) – VLAZ Nov 11 '22 at 07:30
  • @RAJPAWAR I have added the reason in my answer,**because when you invoke `showList()` again,it created new elements and without click event binding to them** – flyingfox Nov 11 '22 at 07:31
  • @VLAZ,thanks for pointing out,I would suggest you to post it as an answer – flyingfox Nov 11 '22 at 07:32
  • Here's an [example implementation](https://stackoverflow.com/a/74399328/636077) of @VLAZ's event delegation suggestion that eliminates the need for adding and re-adding the listeners. – ray Nov 11 '22 at 14:54
0

it's because listener is removed... if I follow the steps

showList(5);

will render the list after that

const listItem = document.querySelectorAll('.list-item'); 
  listItem.forEach((item) => {
    item.addEventListener('click', (e) => {
      console.log(e.target.innerText);
      showList(4);
  }); 
});

this will add listener to each item. but if you click, it will call showList again and it will rerender the list and remove the listener..

so if you want to the listener is added every render, move it to the showList function

function showList(num) {
        const taskBox = document.querySelector('.task-list'); 
        let listHtml = "";
                
        for(let i =1;i<=num;i++){
            listHtml +=  `
                <li class="list-item">Hello ${i}</li>
            `;
        }

        taskBox.innerHTML = listHtml; 
        const listItem = document.querySelectorAll('.list-item'); 
        listItem.forEach((item) => {
            item.addEventListener('click', (e) => {
                console.log(e.target.innerText);
                showList(4);
            }); 
        }); 
    }
0

Here's an implementation using @VLAZ's event delegation suggestion.

Put the click listener on the container and use the event to identify what was clicked.

function showList(num) {
    const taskBox = document.querySelector('.task-list'); 
    let listHtml = "";
            
    for(let i =1;i<=num;i++){
        listHtml += `<li class="list-item">Hello ${i}</li>`;
    }

    taskBox.innerHTML = listHtml;
}

// listen on the container and use the
// event to figure out what was clicked
// instead of adding a listener on every
// child.
document.querySelector('.task-list').addEventListener(
  'click',
  (e) => { console.log(e.target.innerText) }
)

showList(5);
<div class="task-list">
        
</div>
ray
  • 26,557
  • 5
  • 28
  • 27