Delegation Pattern
Event delegation is a technique where you attach a single event listener to a parent element to handle events for all its children, leveraging event bubbling.
The Problem
<ul id="todo-list">
<li>Item 1 <button class="delete">X</button></li>
<li>Item 2 <button class="delete">X</button></li>
<li>Item 3 <button class="delete">X</button></li>
</ul>
Without delegation:
// Need to add listener to EACH button
document.querySelectorAll('.delete').forEach(btn => {
btn.addEventListener('click', (e) => {
e.target.closest('li').remove();
});
});
// Problem: dynamically added items won't have listeners!
The Solution
// Single listener on parent
document.getElementById('todo-list').addEventListener('click', (e) => {
if (e.target.classList.contains('delete')) {
e.target.closest('li').remove();
}
});
// Now works for ALL current and future items!
How It Works
- Event bubbles from child to parent
- Parent listener catches the event
- Check
e.targetto identify which child triggered it - Execute appropriate handler
Implementation
Here are common delegation patterns and implementations.
Basic Delegation
function delegate(parent, eventType, selector, handler) {
parent.addEventListener(eventType, (e) => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e, target);
}
});
}
// Usage
delegate(document.getElementById('list'), 'click', '.item', (e, item) => {
console.log('Item clicked:', item.textContent);
});
Using closest()
// Find the closest matching ancestor
document.getElementById('table').addEventListener('click', (e) => {
const row = e.target.closest('tr');
const cell = e.target.closest('td');
if (row) {
console.log('Row clicked:', row.rowIndex);
}
if (cell) {
console.log('Cell clicked:', cell.cellIndex);
}
});
Multiple Event Types
function delegateMultiple(parent, handlers) {
Object.entries(handlers).forEach(([eventType, config]) => {
parent.addEventListener(eventType, (e) => {
Object.entries(config).forEach(([selector, handler]) => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e, target);
}
});
});
});
}
// Usage
delegateMultiple(document.getElementById('form'), {
click: {
'.submit-btn': (e) => submitForm(),
'.cancel-btn': (e) => cancelForm()
},
input: {
'input[name]': (e) => validateField(e.target),
'select': (e) => updateOptions(e.target)
}
});
Data Attribute Pattern
// Use data attributes for event handlers
document.addEventListener('click', (e) => {
const action = e.target.dataset.action;
if (action) {
actions[action](e);
}
});
const actions = {
delete: (e) => e.target.closest('.item').remove(),
edit: (e) => editItem(e.target.closest('.item')),
save: (e) => saveItem(e.target.closest('.item'))
};
// HTML
// <button data-action="delete">X</button>
// <button data-action="edit">Edit</button>
Benefits and Tradeoffs
Event delegation has advantages and disadvantages to consider.
Benefits
1. Memory Efficiency
// Without delegation: 1000 items = 1000 listeners
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// With delegation: 1000 items = 1 listener
document.querySelector('.list').addEventListener('click', (e) => {
if (e.target.closest('.item')) {
handleClick(e);
}
});
2. Dynamic Content
// Delegation works for elements added after page load
function addItem(text) {
const li = document.createElement('li');
li.textContent = text;
list.appendChild(li); // Automatically handled!
}
3. Cleaner Code
// Less setup code
// No need to re-bind listeners when DOM changes
// Single point of control
Tradeoffs
1. Indirect Target Identification
// Must check e.target, not rely on 'this'
list.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (!item) return;
// 'this' is the list, not the item
});
2. Complexity with Deep Nesting
// May need complex selector logic
document.addEventListener('click', (e) => {
const button = e.target.closest('button, [role="button"]');
if (!button) return;
const form = button.closest('form');
if (!form) return;
// Handle based on button type
});
3. Debugging Challenges
// Harder to trace which handler is called
// Solution: add data attributes for debugging
button.dataset.handler = 'delete';
Best Practices
// 1. Use specific selectors
document.addEventListener('click', (e) => {
const item = e.target.closest('.list-item');
if (!item || !list.contains(item)) return;
// Handle item
});
// 2. Check parent containment
if (!parent.contains(e.target)) return;
// 3. Use data attributes for clarity
<button data-action="delete" data-id="123">Delete</button>
Practice Problems
Create a reusable React component implementing Event Delegation. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Event Delegation using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Event Delegation for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is event delegation?
2. Why is event delegation useful for dynamic content?
3. How do you identify which child element triggered an event in delegation?
4. What is a tradeoff of event delegation?
Flashcards
Question
What is event delegation?
Click to reveal answer
Answer
A pattern where a single listener on a parent handles events for all children using event bubbling.
Question
Why use event delegation?
Click to reveal answer
Answer
Memory efficiency (fewer listeners), works with dynamic content, cleaner setup code.
Question
How to find the triggering element?
Click to reveal answer
Answer
Use e.target.closest(selector) to find the relevant child element.
Question
What is a tradeoff of delegation?
Click to reveal answer
Answer
More complex target identification and potential for harder debugging.
Question
What is Event Delegation?
Click to reveal answer
Answer
Event Delegation is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Event delegation leverages bubbling to handle child events at the parent
- 2.Uses fewer listeners and works with dynamic content
- 3.Identify targets with e.target.closest()
- 4.Tradeoff is more complex target identification
- 5.Great for lists, tables, and dynamic UI components
Interview Tips
- •Explain how delegation works with event bubbling
- •Show how to implement delegation for dynamic content
- •Discuss when delegation is beneficial vs direct listeners
- •Demonstrate using closest() for target identification
Cheat Sheet
Event Delegation Cheat Sheet
What is it?
Single listener on parent handles all child events via bubbling.
Basic Pattern
parent.addEventListener('click', (e) => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler(target, e);
}
});
Benefits
- Memory efficient
- Works with dynamic content
- Cleaner setup
Tradeoffs
- Complex target identification
- Harder debugging
Best Practices
- Use specific selectors
- Check parent containment
- Use data attributes for clarity