How to Bind Event Listeners to Dynamically-Created Elements in JavaScript
Nick Scialli
May 08, 2021
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:
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!
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:
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.
Nick Scialli is a senior UI engineer at Microsoft.