Event Delegation
Event delegation = listen on parent, handle on child
What is Event Delegation?
"Listen on parent, handle child events"
Event delegation attaches an event listener to a parent element instead of individual child elements.
"Event delegation attaches event listeners to a parent element, using event bubbling to handle events from child elements."
How It Works
Event bubbling enables delegation
// Listen on parent
document.getElementById("list").addEventListener("click", (e) => {
// Check if clicked element is a list item
if (e.target.tagName === "LI") {
console.log("List item clicked:", e.target.textContent);
}
});
Events bubble up from child to parent.
"Event delegation works because events bubble up from child to parent, allowing the parent to handle child events."
Benefits
Performance · Dynamic content · Memory
- Performance: One listener instead of many
- Dynamic content: Works with added elements
- Memory: Fewer event listeners
- Simpler: Less code to manage
"Event delegation improves performance with fewer listeners, works with dynamically added elements, and uses less memory."
Example: List Items
One listener for all items
// ❌ Without delegation - listener per item
items.forEach((item) => {
item.addEventListener("click", handleClick);
});
// ✅ With delegation - one listener
list.addEventListener("click", (e) => {
if (e.target.matches("li")) {
handleClick(e.target);
}
});
"Event delegation uses one listener on the parent instead of multiple listeners on each child."
Dynamic Content
Works with elements added later
// Listener on parent
list.addEventListener("click", handleClick);
// Add new item later - still works!
const newItem = document.createElement("li");
newItem.textContent = "New Item";
list.appendChild(newItem);
"Event delegation automatically works with dynamically added elements without adding new listeners."
Event Target
e.target = actual clicked element
parent.addEventListener("click", (e) => {
console.log(e.target); // The actual clicked element
console.log(e.currentTarget); // The parent (listener)
});
"e.target is the element that triggered the event, e.currentTarget is the element with the listener."
Matching Elements
Check e.target with matches() or tagName
parent.addEventListener("click", (e) => {
// Check tag name
if (e.target.tagName === "BUTTON") {
// Handle button click
}
// Check class
if (e.target.matches(".button")) {
// Handle button click
}
// Check closest parent
const button = e.target.closest(".button");
if (button) {
// Handle button click
}
});
"Use e.target.tagName, e.target.matches(), or e.target.closest() to identify which child was clicked."
When to Use (with React Example)
Many elements · Dynamic content
Use event delegation (which React does automatically) when:
- Many similar elements (e.g. large lists)
- Elements might be dynamically added/removed
- Performance is important
- Elements share a common parent
"Use event delegation for lists, tables, or any scenario with many similar or dynamic elements."
🧩 React Example: Good Fit for Delegation
function TodoList({ items, onItemClick }) {
return (
<ul
onClick={(e) => {
// Delegate: only respond if LI is clicked
if (e.target.tagName === "LI") {
onItemClick(e.target.dataset.id);
}
}}
>
{items.map((item) => (
<li key={item.id} data-id={item.id}>
{item.text}
</li>
))}
</ul>
); // No need to attach an onClick to every <li>
}
- Only one onClick handler for the whole list, fast even with lots of items.
- Works with new items added to the list.
When NOT to Use (with React Example)
Simple cases · Need event.stopPropagation()
Avoid event delegation when:
- Very few elements (no real performance benefit)
- You need to independently handle/cancel event propagation for certain elements
- Each element needs separate, complex handlers
"Avoid event delegation for simple situations with few elements or when you need direct control over each event."
🧩 React Example: NOT a Good Fit for Delegation
function ButtonGroup() {
// Each button has its own logic
return (
<div>
<button onClick={() => alert("Save")}>Save</button>
<button onClick={() => alert("Cancel")}>Cancel</button>
</div>
);
}
- Each button has its own onClick.
- Makes reasoning and event control simpler for a small number of elements.
- Easier to handle things like event.stopPropagation() as needed.
🧠 Ultra-Short Cheat Sheet
Listen on parent, handle children
Event bubbling
e.target = clicked element
Performance benefit
Works with dynamic content
matches() / closest() to filter