Skip to content
intermediatePhase 33 · Advanced JavaScript

Event Delegation

Use event bubbling to handle events efficiently on parent elements.

30m
0 problems
Topic Progress0%

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

  1. Event bubbles from child to parent
  2. Parent listener catches the event
  3. Check e.target to identify which child triggered it
  4. 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

0/3solved
Build Event Delegation Component

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 needed
Event Delegation Testing

Write 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 tests
Event Delegation Performance

Optimize 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 analysis

Quiz

1. What is event delegation?

Question 1 options

2. Why is event delegation useful for dynamic content?

Question 2 options

3. How do you identify which child element triggered an event in delegation?

Question 3 options

4. What is a tradeoff of event delegation?

Question 4 options

Flashcards

Question

What is event delegation?

Answer

A pattern where a single listener on a parent handles events for all children using event bubbling.

Question

Why use event delegation?

Answer

Memory efficiency (fewer listeners), works with dynamic content, cleaner setup code.

Question

How to find the triggering element?

Answer

Use e.target.closest(selector) to find the relevant child element.

Question

What is a tradeoff of delegation?

Answer

More complex target identification and potential for harder debugging.

Question

What is Event Delegation?

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