Frontend Development › JavaScript & TypeScript
Event Delegation
One listener on a parent handling events for many children.
Also known as: delegated events, delegate event listener
Event delegation means attaching one event listener to a parent element, instead of one to each child, and using event bubbling to handle events from the children.
// Instead of this: a listener per item
document.querySelectorAll("#todo li").forEach((li) =>
li.addEventListener("click", () => toggle(li))
);
// Use one on the parent
document.querySelector("#todo").addEventListener("click", (e) => {
const li = e.target.closest("li");
if (!li) return; // the click was elsewhere
toggle(li.dataset.id);
});
It works because a click on an <li> bubbles up to the <ul> (event bubbling). e.target tells you which child it came from.
Why use it
- Works for elements added later. New items inserted into the list are handled automatically, with no need to attach listeners again.
- Fewer listeners, so less memory and setup for long lists.
- Simpler cleanup: one listener to remove.
Details to get right
- Use
closest(), note.targetalone. The target may be a child inside your item (an icon in a button), andclosestfinds the item you care about. - Check the match is inside your container when nested structures are possible.
- Identify items with data attributes:
data-id,data-action. - Pick events that bubble.
click,input,keydown,submitdo.focus,blurandmouseenterdon’t, so usefocusin,focusoutormouseover. - Don’t delegate to
documentfor everything. Choose the closest sensible parent. stopPropagation()in a child will prevent the parent from seeing the event.
<ul id="todo">
<li data-id="1">Buy milk <button data-action="delete">x</button></li>
</ul>
In frameworks like React, delegation happens under the hood, and you attach handlers on elements normally. But it’s still the right tool in plain JavaScript for dynamic lists.