Using jQuery .on() for Event Delegation on Dynamically Added Elements
Learn how to attach a single event listener to a static parent and let it handle clicks on current and future list items, reducing overhead and simplifying code.
09 Nov 2025, 01:37 UTC

The problem: attaching listeners to every new element
Imagine a simple task list where users can add new items via a form or an AJAX call. Each <li> needs a click handler to toggle its completion state. The naïve approach is to attach a listener directly to every <li> as it is created:
$('#addButton').on('click', function () {
var $newItem = $('New task ');
$newItem.on('click', handleClick);
$('#taskList').append($newItem);
});
If the list grows to hundreds or thousands of items, each item carries its own listener, increasing memory use and making event‑heavy interactions (like scrolling or resizing) feel sluggish.
The thesis: a single delegated listener handles present and future matches
jQuery’s .on() method, introduced in version 1.7, lets you place one listener on a static ancestor and specify a selector that jQuery evaluates at event time. When an event bubbles up from a descendant, the handler runs only if the descendant matches the selector—whether that element existed when the listener was attached or is added later.
Worked example: delegating clicks to a dynamic list
First, set up a static container:
<ul id="taskList"></ul>
<button id="addButton">Add item</button>
Then attach a delegated click listener to the <ul>:
$('#taskList').on('click', 'li', function () {
$(this).toggleClass('completed');
});
Now add items dynamically:
$('#addButton').on('click', function () {
$('#taskList').append($('New task '));
});
Clicking any <li>—whether it was in the original markup or added after the page loaded—toggles the completed class. No listeners are attached directly to the <li> elements; you can verify this in the browser console by checking that $('#taskList li').data('events') is empty while a click event exists on #taskList.
Trade‑offs and limitations
- Selector specificity matters. Using a very broad delegate target like
$(document)or$('body')forces every matching event to bubble all the way up to that element. On pages with frequent events (e.g.,mousemove) this can degrade performance. - Cleanup requires the same signature. To remove a delegated handler you must call
.off()with the exact same selector and handler function; otherwise the delegate remains attached, which can cause memory leaks in single‑page applications. - Event timing. The selector is evaluated at event time, so if you change the DOM structure of a matching element after the event has started bubbling, the handler may not behave as expected. This is rarely an issue for simple click or key events but worth noting for custom events.
Actionable closing
When you need to respond to user actions on elements that may appear after the initial page load, reach for .on() with a delegate selector. Attach the listener to the nearest static container that encloses the dynamic content, keep the selector as specific as practical, and remember to clean up with .off() when the container itself is removed. This pattern reduces listener overhead, simplifies your code, and scales gracefully as your interface grows.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.