Handle Clicks on Dynamically Added Elements with jQuery Event Delegation
Bind one handler to a stable container instead of re-binding every new element.
07 Sept 2026, 22:28 UTC

The problem: your click handler misses new elements
You bind a click handler to .comment-button on page load. Later, your app fetches comments and appends new buttons to the DOM. Clicking those new buttons does nothing. The original binding only attached to elements that existed when the script ran.
Rebinding after every insertion works, but it creates duplicate handlers, wastes memory, and is easy to forget.
The takeaway: delegate once from a stable ancestor
Use jQuery's .on(events, selector, handler) form. Attach the handler to an ancestor that exists when the page loads, and tell jQuery which descendant selector should trigger it. jQuery monitors bubbling events at the ancestor and runs the handler when the event originated from a matching descendant—even if that descendant was added after the binding call.
This pattern has been the unified replacement for .live() and .delegate() since jQuery 1.7 and remains supported in jQuery 3.x standard and slim builds.
Worked example: comment buttons added by AJAX
Assume a page with this markup:
<div id="comment-list">
<button class="reply-btn">Reply</button>
</div>
Run this script after jQuery loads, for example in a <script> tag at the end of <body> or from the browser console. No special permissions are required beyond the ability to execute JavaScript on the page.
$('#comment-list').on('click', '.reply-btn', function (event) {
event.preventDefault();
const $button = $(event.target);
console.log('Reply clicked:', $button.text());
});
Later, when your application appends a new button:
$('#comment-list').append('<button class="reply-btn">Reply</button>');
Clicking the newly appended button still runs the handler. You do not call .on() again.
Where to run: browser DevTools console, or a page script after jQuery is loaded. Expected check: typeof jQuery.fn.on === 'function' returns true, and clicking a dynamically added .reply-btn logs the message. Risk: if a descendant handler calls event.stopPropagation(), the event never reaches the delegated ancestor, so your handler will not run.
Important limits and diagnostics
- Bubbling only. Delegation relies on event bubbling.
focus,blur,mouseenter, andmouseleavedo not bubble natively. Usefocusin/focusoutormouseover/mouseoutinstead. event.targetvsevent.currentTarget.event.targetis the element that originated the event, for example the button.event.currentTargetis the delegated ancestor, for example#comment-list.- Container lifetime. If you remove and recreate
#comment-listentirely, the delegated binding is lost because it was attached to the old container. Re-attach to the new container. - Memory. One handler on a container replaces many handlers on individual dynamic elements, reducing handler count and memory overhead.
Quick verification in the console
- Confirm jQuery and
.on()exist:
console.log(jQuery.fn.jquery);
console.log(typeof jQuery.fn.on === 'function');
- Bind the delegated handler:
$('#comment-list').on('click', '.reply-btn', function () {
console.log('delegated handler ran');
});
- Append a matching element and click it:
$('#comment-list').append('<button class="reply-btn">New</button>');
If the console logs the message without re-binding, delegation is working. If nothing happens, check whether the container was recreated, whether the selector matches, or whether another handler stopped propagation.
Closing
Bind once to a stable ancestor, let the event bubble, and match the dynamic selector inside .on(). It keeps your code simpler and your pages lighter. Verify with a console click test after appending new markup, and switch to bubbling alternatives for focus or mouseenter events.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.