JS Use for loop variable in function definition, without the function changing during iterations

I am coding a game in which the user must select one of the cards sent from the back-end, but I don’t know what cards, or how many, until I recieve them. I must make a button for each, and sent the chosen one to the back end. Since I don’t know how many cards there will be, I define each button’s action in a for loop.

Since I use the loop variable to acces the numerical code that must be sent to the back-end, after each iteration of the loop the numerical code sent by the function changes.

Here’s how I solved the problem:

(cards is an array with the cards, and for any card in it, card[‘code’] is it’s numerical code. The 11 in sendJSON means that the info sent is the chosen card)

for (var i = 0; i < cards.length; i++) {
    container.innerHTML += `<button><img src="Cards/${cards[i]['code'].toString()}.png" width="346" height="529" id="${cards[i]['code'].toString()}"></button>`;

    // Here is the problem:
    eval('document.getElementById(' + cards[i]['code'].toString() + ').onclick = async function f() {await sendJSON(11, ' + cards[i]['code'] + ')}')
}

The problem is the eval() since not only it is unsafe, it also has bad performance, so I was wondering…

Is there a better way to do this?

Answer

Since you’re putting the code in the id attribute, the event listener can simply use this.id to get it, you don’t need to copy it into the function.

You also need to use insertAdjacentHTML() rather than concatenating to innerHTML. When you concatenate, all the HTML in the container is re-parsed, and any event listeners on previous images will be lost. insertAdjacentHTML() only parses the new HTML and leaves all the old elements unchanged.

for (var i = 0; i < cards.length; i++) {
  container.insertAdjecentHTML('beforeend', `<button><img src="Cards/${cards[i]['code']}.png" width="346" height="529" id="${cards[i]['code']}"></button>`);

  document.getElementById(cards[i]['code']).addEventListener("click", async function f() {
    await sendJSON(11, this.id)
  });
}

There’s also no need for all the toString() calls. Substituting into a template literal automatically converts it to a string.