Jjkivai
Jjkivai

Reputation: 157

Click event listener fires multiple times

I have a nested <i> tag that shows liked or unliked heart. The problem is that every time a post is liked it sends around 16 requests (event fires 16 times). I have tried using sleep, and seTimeout to no avail. I need to dynamically add the event listener to the <i> tag.

initial_html

{% if user in post.get_likers%}
        <p class="my-0 text-muted"><i style="color: red;" class="fas fa-heart hover"></i> {{post.total_likes}}</p>
{% else %}
        <p class="my-0 text-muted"><i class="far fa-heart hover"></i> {{post.total_likes}}</p>
{% endif %}

Attempt to update likes per view

document.addEventListener("DOMContentLoaded", () => {
  load_clickable();
});

function load_clickable() {
  document.querySelectorAll("i").forEach((item) => {
    item.addEventListener("click", () => {
      update_likes(item);
    });
  });
}

function update_likes(item) {
  const p_tag = item.parentNode;
  const post_id = p_tag.parentNode.id;
  console.log(post_id, p_tag.innerText);
  const fetch_url = `/like/${post_id}`;
  fetch(fetch_url, {
    method: "POST",
  })
    .then((response) => response.json())
    .then((result) => {
      if (result.like) {
        console.log("Has Liked");
        var new_likes = parseInt(p_tag.innerText) + 1;
        p_tag.innerHTML = `<i style="color: red;" class="fas fa-heart hover"></i> ${new_likes}`;
      } else if (result.unlike) {
        console.log("Has unliked");
        var new_likes = parseInt(p_tag.innerText) - 1;
        p_tag.innerHTML = `<i class="far fa-heart hover"></i> ${new_likes}`;
      }
      load_clickable();
    });
}

How can I make it such that each event fires once?

Upvotes: 2

Views: 214

Answers (2)

Daudidenno
Daudidenno

Reputation: 47

calling the load_clickable method is the problem, the click event is technically still active, why not just load the onclick attr to the new i tag, like so

.then((result) => {
      if (result.like) {
        console.log("Has Liked");
        var new_likes = parseInt(p_tag.innerText) + 1;
        p_tag.innerHTML = `<i style="color: red;" class="fas fa-heart hover" onclick=update_likes(this)></i> ${new_likes}`;
      } else if (result.unlike) {
        console.log("Has unliked");
        var new_likes = parseInt(p_tag.innerText) - 1;
        p_tag.innerHTML = `<i class="far fa-heart hover" onclick=update_likes(this)></i> ${new_likes}`;
      }
    });

best solution

{% if user in post.get_likers%}
        <p class="my-0 text-muted"><i style="color: red;" class="fas fa-heart hover onclick=update_likes()"></i> <span>{{post.total_likes}}</span></p>
{% else %}
        <p class="my-0 text-muted"><i class="far fa-heart hover onclick=update_likes()"></i> <span>{{post.total_likes}}</span></p>
{% endif %}

then javascript

document.addEventListener("DOMContentLoaded", () => {
    //do something else with loaded DOM
});

function update_likes() {
  const p_tag = this.parentNode;
  const post_id = p_tag.parentNode.id;
  console.log(post_id, p_tag.innerText);
  const fetch_url = `/like/${post_id}`;
  fetch(fetch_url, {
    method: "POST",
  })
    .then((response) => response.json())
    .then((result) => {
      if (result.like) {
        console.log("Has Liked");
        var new_likes = parseInt(p_tag.innerText) + 1;
        p_tag.innerHTML = `<i style="color: red;" class="fas fa-heart hover" onclick=update_likes(this)></i> ${new_likes}`;
      } else if (result.unlike) {
        console.log("Has unliked");
        var new_likes = parseInt(p_tag.innerText) - 1;
        p_tag.innerHTML = `<i class="far fa-heart hover" onclick=update_likes(this)></i> ${new_likes}`;
      }
    });
}

this way all tags are already rendered with their tags

Upvotes: 1

Unmitigated
Unmitigated

Reputation: 89264

The issue is that you are calling load_clickable after updating the likes, which adds event listeners to all of the <i> elements again. Instead, you should display the amount of likes inside a <span> and update the textContent each time so that you do not need to attach all the event listeners again.

Updated HTML:

{% if user in post.get_likers%}
        <p class="my-0 text-muted"><i style="color: red;" class="fas fa-heart hover"></i> <span>{{post.total_likes}}</span></p>
{% else %}
        <p class="my-0 text-muted"><i class="far fa-heart hover"></i> <span>{{post.total_likes}}</span></p>
{% endif %}

Updated JavaScript (with parts omitted for brevity):

//...
.then((result) => {
      if (result.like) {
        console.log("Has Liked");
        var new_likes = parseInt(p_tag.innerText) + 1;
        p_tag.querySelector('span').textContent = new_likes;
      } else if (result.unlike) {
        console.log("Has unliked");
        var new_likes = parseInt(p_tag.innerText) - 1;
        p_tag.querySelector('span').textContent = new_likes;
      }
    });
//...

Upvotes: 2

Related Questions