Skip to main content

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