Using .onclick on dynamically created button

I am having some real difficulty with using .onclick on a group of dynamically created buttons. I was having the same problem with this yesterday on a real project that I’m working on so decided to trial out on this practice project that I have and the problem still persists so this is definitely a me problem.

At first I thought maybe it was to do with how I am creating the buttons. So this was how I was creating it in the beginning, like this:

function userData() {
    fetch("https://jsonplaceholder.typicode.com/users")
    .then(response => response.json())
    .then(users => {
        let output = '<h6>List of Users</h6>';
        output += '<div class="list-group">';
        users.forEach(function(user) {
            output += `
                <button data-user="${user.id} type="button" class="list-group-item list-group-item-action">
                    <strong>${user.name}</strong><br>
                    ${user.email}<br>
                    ${user.address.city}<br>
                </button>
            `;
        });
        output += '</div>';
        document.querySelector('#response').innerHTML = output;
        });
}

I thought this probably wasn’t the best way to go about it so I decided to use the document.createElement method instead and now it all looks like this:

document.addEventListener('DOMContentLoaded', function() {
userData();
document.querySelectorAll('button').forEach(button => {
    button.onclick = function(){
        userSelect(this.dataset.user_id);
    }
})

});


function userData() {
    fetch("https://jsonplaceholder.typicode.com/users")
    .then(response => response.json())
    .then(users => {
        const h6 = document.createElement("h6");
        h6.innerText = "List of Users";
        const userList = document.createElement("div");
        userList.className = "list-group";
        users.forEach(function(user) {
            const userButton = document.createElement("button");
            userButton.className = "list-group-item list-group-item-action";
            userButton.setAttribute('data-user', `${user.id}`);
            userButton.innerHTML = `
            <strong>${user.name}</strong><br>
            ${user.email}<br>
            ${user.address.city}<br>
            `;
            userList.appendChild(userButton);
        });
        const container = document.querySelector('#response');
        container.appendChild(h6);
        container.insertBefore(userList, h6.nextSibling);
    });
}


function userSelect(user_id) {
    fetch(`https://jsonplaceholder.typicode.com/users/${user_id}`)
    .then(response => response.json())
    .then(user => {
        console.log(user);
    });
}

The problem is when I click on a button, nothing is showing in the console but when I directly use the function: userSelect(6); that user object shows up fine. I am really going wrong somewhere with the .onclick but I really don’t know what’s wrong. If I can work out how to solve it here, I know I’ll be able to resolve the problem with the project I’m working on.

Hi,

The reason your event handlers are doing nothing is that you are attempting to attach them before the DOM elements have been added to the page.

document.addEventListener('DOMContentLoaded', () => {
  userData();
  
  // userData performs an asynchronous operation
  // the JS interpreter doesn't wait for this to be completed
  // so when you try and attach your event handler
  // buttons is an empty NodeList
  const buttons = document.querySelectorAll('button');
  console.log(buttons);

  document.querySelectorAll('button').forEach((button) => {
    button.onclick = function () {
      console.log('here');
      userSelect(this.dataset.user_id);
    };
  });
});

There are a number of ways to get around this. One would be to attach the event handler to the parent container. When that receives a click, check to see if it was triggered by a button:

function userData() {
  fetch('https://jsonplaceholder.typicode.com/users')
    .then((response) => response.json())
    .then((users) => {
      const h6 = document.createElement('h6');
      h6.innerText = 'List of Users';
      const userList = document.createElement('div');
      userList.className = 'list-group';
      users.forEach((user) => {
        const userButton = document.createElement('button');
        userButton.className = 'list-group-item list-group-item-action';
        userButton.setAttribute('data-user', `${user.id}`);
        userButton.innerHTML = `
            <strong>${user.name}</strong><br>
            ${user.email}<br>
            ${user.address.city}<br>
            `;
        userList.appendChild(userButton);
      });
      const container = document.querySelector('#response');
      container.appendChild(h6);
      container.insertBefore(userList, h6.nextSibling);
    });
}

function userSelect(user_id) {
  fetch(`https://jsonplaceholder.typicode.com/users/${user_id}`)
    .then((response) => response.json())
    .then((user) => {
      console.log(user);
    });
}

document.addEventListener('DOMContentLoaded', () => {
  userData();

  document.querySelector('#response');
  response.addEventListener('click', (e) => {
    if(e.target.nodeName === 'BUTTON') userSelect(e.target.dataset.user);
  });
});

This will work as you intended.

Hi @sarahamachree, you’re fetching the user data here, and while waiting for the response (fetch(), being asynchronous, returns a promise) you’re immediately querying for the buttons that don’t exist yet. So you’ll either have to wait for the result:

function fetchUserData () {
  return fetch('https://jsonplaceholder.typicode.com/users')
    .then(response => response.json())
    .then(users => {
      // ...
      users.forEach(function (user) {
        const userButton = document.createElement('button')
        userButton.className = 'list-group-item list-group-item-action'
        userButton.setAttribute('data-user', `${user.id}`)
        // ...
      })
    })
}

document.addEventListener('DOMContentLoaded', function () {
  fetchUserData().then(() => {
    document.querySelectorAll('button').forEach(button => {
      button.onclick = function () {
        userSelect(this.dataset.user)
      }
    })
  })
})

Edit: Also note that it should be this.dataset.user, not user_id.

Or actually add the event listener right away, so that you don’t have to query for the buttons again that you just created:

function fetchUserData () {
  return fetch('https://jsonplaceholder.typicode.com/users')
    .then(response => response.json())
    .then(users => {
      // ...
      users.forEach(function (user) {
        const userButton = document.createElement('button')
        
        userButton.addEventListener('click', () => userSelect(user.id))
      })
    })
}

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.