Event binding on dynamic HTML, using fetch and within forEach

holgerder125

I am trying to add an event listener on the HTML element show belown, generated dynamically within a forEach loop. I'm getting the proper response from the JSON Placeholder API initially, however, none of my attempts to bind an event on any of the HTML elements I generate after the initial response seem to produce result when I try out the event.

I'm either getting no 'Clicked' at all or ... addEventListener is not a function, depending on where I put the addEventListener code.

Is there any way to make this possible in a clean and reusable way? Also, is this the preferred way of generating dynamic HTML through vanilla JS? Or should I generate it manually, by using document.createElement() and appendChild() for example?

Ultimately, I am meaning to pass the ID of the clicked target to the getPosts function fetch URL.

Any help would be greatly appreciated!

fetch('https://jsonplaceholder.typicode.com/users')
  .then(res => res.json())
  .then(data => createResult(data));

function createResult(data) {
  const container = document.getElementById('result');

  data.forEach((user) => {
    const { id, name, email, address: { city, street } } = user;

    let result =
      `<div class="user" data-uid=${id}>
          <h5 id="user-${id}"> User ID: ${id} </h5>
            <ul class="w3-ul">
              <li> User Full Name : ${name}</li>
              <li> User Email : ${email} </li>
              <li> User Address : ${city}, ${street} </li>
            </ul>
        </div>`;

    container.innerHTML += result;
    
    document.body.addEventListener("click", function (e) {
      if (e.target &&
        e.target.classList.contains("user")) {
        console.log('Clicked');
      }
    });
  });
}

function getPosts(e) {
  fetch(`https://jsonplaceholder.typicode.com/users/${user.id}`)
    .then(res => res.json())
    .then(data => console.log(data));
}
<div id=result></div>

Andreas

There are two problems with your approach. The first problem is that you add the click handler to document.body for every element in data.

If you call createResult() only once then move the .addEventListener() call before or after the .forEach()

container.addEventListener("click", ...);
data.forEach(...);

If you call createResult() multiple times then move the .addEventListener() out of it and maybe call it on DOMContentLoaded

window.addEventListener("DOMContentLoaded", () => {
  document.getElementById('result').addEventListener("click", ...);
});

The second problem is e.target. e.target is the element that triggered the click event. And this most likely wont be div.result but an element in div.result. You have to traverse up the DOM until you find div.result. A possible way can be to use Element.closest() or a loop and Node.parentElement.

fakeFetch().then(createResult);

function createResult(data) {
  const container = document.getElementById('result');

  container.addEventListener("click", function (e) {
    const user = e.target.closest(".user");
    
    if (user) {
      getPosts(user.dataset.uid);
    }
  });

  data.forEach((user) => {
    const { id, name, email, address: { city, street } } = user;

    let result =
      `<div class="user" data-uid="${id}">
          <h5 id="user-${id}"> User ID: ${id} </h5>
            <ul class="w3-ul">
              <li> User Full Name : ${name}</li>
              <li> User Email : ${email} </li>
              <li> User Address : ${city}, ${street} </li>
            </ul>
        </div>`;

    container.innerHTML += result;
  });
}

function getPosts(userId) {
  console.log(userId);
}

function fakeFetch() {
  return new Promise(resolve => {
    const user = [
      { id: 1, name: "a", mail: "[email protected]", address: { city: "a", street: "a" } },
      { id: 2, name: "b", mail: "[email protected]", address: { city: "b", street: "b" } }
    ];
    
    setTimeout(resolve(user), 1000);
  })
}
<div id="result"></div>

And as a second note:
Don't use .innerHTML. This will overwrite the content of the element and therefor remove any event handlers. Use Element.insertAdjacentHTML() instead.

container.innerHTML += result

would then be

container.insertAdjacentHTML("beforeend", result);

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related