TypeOfNaN

How to Bind Event Listeners to Dynamically-Created Elements in JavaScript

Nick Scialli May 08, 2021🚀 4 minute read

You may have noticed that new elements you dynamically create don’t automatically have the event listeners that its peers have. In this post, we’ll leverage event bubbling to make sure we never have this problem.

An Example Problem

Let’s say we have a list of buttons and, when we click one of the buttons, it will alert its own text. In this example, we will start out with two buttons in the DOM. They will have the class alert-button, and we’ll use that class to add the alerting functionality:

<div id="alert-container">
  <button class="alert-button">Button 1</button>
  <button class="alert-button">Button 2</button>
</div>

<script>
  const btns = document.querySelectorAll('.alert-button');
  for (let i = 0; i < btns.length; i++) {
    btns[i].addEventListener('click', function (e) {
      alert(e.target.innerHTML);
    });
  }
</script>

Sure enough, this works:

two buttons alerting

But what if we now need to dynamically add a third button? We can see that our newly added button doesn’t work, even if we give it the same class as the other buttons.

<div id="alert-container">
  <button class="alert-button">Button 1</button>
  <button class="alert-button">Button 2</button>
</div>

<script>
  const btns = document.querySelectorAll('.alert-button');
  for (let i = 0; i < btns.length; i++) {
    btns[i].addEventListener('click', function (e) {
      alert(e.target.innerHTML);
    });
  }

  // Add a new button dynamically
  const newBtn = document.createElement('button');
  newBtn.classList.add('alert-button');
  newBtn.innerHTML = 'Button 3';
  const container = document.querySelector('#alert-container');
  container.append(newBtn);
</script>

And this new button does nothing!

button 3 doing nothing

How Event Listeners Work

This is actually expected behavior once you understand how event listeners work. When we initially added an event listener to our two buttons, we used their class names to select those buttons. However, after we have selected the elements, the class names really have nothing more to do with the matter. It’s a misunderstanding to think our click even is somehow tied to the class itself.

In other words: adding a new element of the same class certainly won’t also cause that element to have an event handler! We need to find another way.

A Reasonable Solution: Event Bubbling

Our intuition might be that, whenever we add a new element, we should then go ahead and explicitly add a click handler to that element. But—there’s an easier way. We can use event bubbling: We can add the event listener to an element higher up our DOM hierarchy (in this case, the #alert-container div).

Of course, we don’t want an alert to be triggered for the entire div, just the buttons. There’s an easy fix for this: we simply add a test to make sure the alert is only performed when the clicked element has a class of alert-button.

Let’s see how this code looks:

<div id="alert-container">
  <button class="alert-button">Button 1</button>
  <button class="alert-button">Button 2</button>
</div>

<script>
  const container = document.querySelector('#alert-container');

  // Click handler for entire DIV container
  container.addEventListener('click', function (e) {
    // But only alert for elements that have an alert-button class
    if (e.target.classList.contains('alert-button')) {
      alert(e.target.innerHTML);
    }
  });

  const newBtn = document.createElement('button');
  newBtn.classList.add('alert-button');
  newBtn.innerHTML = 'Button 3';
  container.append(newBtn);
</script>

We can see we added an event listener to the entire container and we only alert when e.target.classList contains alert-button. This works, and we can see it in a smoke test:

all buttons working

Conclusion

If we know we’re going to have dynamically-created elements with event handlers, consider leveraging event bubbling to capture those events higher up in the DOM tree. It’s a great way to make sure every element has the event handler and not have to worry about adding handlers to the newly-created elements.

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

Nick Scialli

Nick Scialli is a software engineer at the U.S. Digital Service.