Skip to content
intermediatePhase 34 · DOM

DOM Event Delegation

Apply event delegation patterns for efficient event handling on dynamic content.

30m
0 problems
Topic Progress0%

Delegation in DOM

Event delegation is essential for handling events on dynamic content efficiently.

The Problem with Direct Listeners

// Adding listeners to each item
const items = document.querySelectorAll('.item');
items.forEach(item => {
  item.addEventListener('click', handleClick);
});

// Problem: dynamically added items won't have listeners!
function addItem(text) {
  const li = document.createElement('li');
  li.className = 'item';
  li.textContent = text;
  list.appendChild(li); // No listener on this one!
}

Solution: Event Delegation

// Single listener on parent
const list = document.querySelector('ul');

list.addEventListener('click', (e) => {
  // Find the closest .item
  const item = e.target.closest('.item');
  
  // Check if it exists and is a child of our list
  if (item && list.contains(item)) {
    handleClick(item);
  }
});

// Now ALL items work, including future ones!
function addItem(text) {
  const li = document.createElement('li');
  li.className = 'item';
  li.textContent = text;
  list.appendChild(li); // Automatically handled!
}

Why closest() is Better Than target

// BAD: Using target directly
list.addEventListener('click', (e) => {
  if (e.target.classList.contains('item')) {
    // Only works if clicking directly on .item
    // Not on children of .item!
  }
});

// GOOD: Using closest()
list.addEventListener('click', (e) => {
  const item = e.target.closest('.item');
  if (item && list.contains(item)) {
    // Works even if clicking on children of .item
  }
});

Practical Example

// Todo list with delegation
document.getElementById('todo-list').addEventListener('click', (e) => {
  const todo = e.target.closest('.todo');
  if (!todo) return;
  
  if (e.target.closest('.delete-btn')) {
    todo.remove();
  } else if (e.target.closest('.edit-btn')) {
    editTodo(todo);
  } else {
    todo.classList.toggle('completed');
  }
});

Event Target

Understanding event.target is crucial for effective delegation.

event.target Properties

list.addEventListener('click', (e) => {
  // The element that triggered the event
  console.log('Target:', e.target);
  console.log('Target tag:', e.target.tagName);
  console.log('Target class:', e.target.className);
  console.log('Target text:', e.target.textContent);
  
  // The element with the listener
  console.log('Current target:', e.currentTarget);
});

Target vs CurrentTarget

<ul id="list">
  <li class="item">Item 1</li>
  <li class="item">Item 2</li>
</ul>
const list = document.getElementById('list');

list.addEventListener('click', (e) => {
  // If you click on Item 1:
  // e.target = <li class="item">Item 1</li>
  // e.currentTarget = <ul id="list">...</ul>
  
  // 'this' also refers to currentTarget
  console.log(this === e.currentTarget); // true
});

Finding the Right Element

// Method 1: closest()
const item = e.target.closest('.item');

// Method 2: matches()
if (e.target.matches('.item')) {
  // Direct match only
}

// Method 3: Check parent hierarchy
function findAncestor(el, selector) {
  while (el && !el.matches(selector)) {
    el = el.parentElement;
  }
  return el;
}

// Method 4: Specific element check
if (e.target.tagName === 'BUTTON') {
  // Clicked directly on a button
}

Data Attributes with Delegation

// HTML: <button data-action="delete" data-id="123">X</button>

document.addEventListener('click', (e) => {
  const action = e.target.dataset.action;
  const id = e.target.dataset.id;
  
  if (action && id) {
    actions[action](id);
  }
});

const actions = {
  delete: (id) => deleteItem(id),
  edit: (id) => editItem(id),
  view: (id) => viewItem(id)
};

Practical Patterns

Common delegation patterns for real-world applications.

Navigation Menu

const nav = document.querySelector('nav');

nav.addEventListener('click', (e) => {
  const link = e.target.closest('a');
  if (!link || !nav.contains(link)) return;
  
  e.preventDefault();
  const page = link.dataset.page;
  navigateTo(page);
  
  // Update active state
  nav.querySelector('.active')?.classList.remove('active');
  link.classList.add('active');
});

Table Row Selection

const table = document.querySelector('table');

// Select row on click
table.addEventListener('click', (e) => {
  const row = e.target.closest('tr');
  if (!row || row === table.querySelector('thead tr')) return;
  
  // Toggle selection
  row.classList.toggle('selected');
  
  // Get selected rows
  const selected = table.querySelectorAll('tr.selected');
  console.log(`${selected.length} rows selected`);
});

// Action buttons in rows
table.addEventListener('click', (e) => {
  const deleteBtn = e.target.closest('.delete-btn');
  if (deleteBtn) {
    const row = deleteBtn.closest('tr');
    const id = row.dataset.id;
    deleteRow(id);
    return;
  }
  
  const editBtn = e.target.closest('.edit-btn');
  if (editBtn) {
    const row = editBtn.closest('tr');
    editRow(row);
  }
});

Modal Dialog

const modal = document.getElementById('modal');

modal.addEventListener('click', (e) => {
  // Close on backdrop click
  if (e.target === modal) {
    closeModal();
    return;
  }
  
  // Handle button clicks
  if (e.target.closest('.close-btn')) {
    closeModal();
  } else if (e.target.closest('.confirm-btn')) {
    confirmAction();
    closeModal();
  }
});

Accordion

const accordion = document.querySelector('.accordion');

accordion.addEventListener('click', (e) => {
  const header = e.target.closest('.accordion-header');
  if (!header) return;
  
  const item = header.parentElement;
  const content = item.querySelector('.accordion-content');
  
  // Toggle this item
  const isOpen = content.style.maxHeight;
  
  // Close all others
  accordion.querySelectorAll('.accordion-content').forEach(c => {
    c.style.maxHeight = null;
  });
  
  // Open this one if it was closed
  if (!isOpen) {
    content.style.maxHeight = content.scrollHeight + 'px';
  }
});

Practice Problems

0/3solved
Build DOM Event Delegation Component

Create a reusable React component implementing DOM 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
DOM Event Delegation Testing

Write unit and integration tests for DOM Event Delegation using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
DOM Event Delegation Performance

Optimize DOM 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. Why use closest() instead of checking e.target directly?

Question 1 options

2. What does e.currentTarget refer to?

Question 2 options

3. Why is event delegation useful for dynamic content?

Question 3 options

4. What's the benefit of delegation over direct listeners?

Question 4 options

Flashcards

Question

Why use event delegation in DOM?

Answer

Fewer listeners, works with dynamic content, better performance for many elements.

Question

Why use closest() instead of target?

Answer

closest() walks up the DOM tree, handling clicks on child elements. target only matches the exact clicked element.

Question

What is e.currentTarget?

Answer

The element that has the event listener attached, as opposed to e.target which triggered the event.

Question

How to check if a delegated event matches?

Answer

Use e.target.closest(selector) and check if the result exists and is contained within the listener element.

Question

What is DOM Event Delegation?

Answer

DOM Event Delegation is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.Event delegation handles events at the parent level
  • 2.closest() is essential for finding the right target
  • 3.Delegation works with dynamically added elements
  • 4.Always verify the target is contained within the parent
  • 5.Use data attributes for clean action routing

Interview Tips

  • Implement delegation for a list with dynamic items
  • Explain why closest() is better than checking target directly
  • Show how to handle multiple action types with delegation
  • Discuss delegation patterns for tables and forms

Cheat Sheet

DOM Event Delegation Cheat Sheet

Pattern

parent.addEventListener('click', (e) => {
  const target = e.target.closest('.selector');
  if (target && parent.contains(target)) {
    // Handle event
  }
});

Why Delegation?

  • Fewer listeners (performance)
  • Works with dynamic content
  • Easier setup and cleanup

target vs currentTarget

  • target: Element that triggered event
  • currentTarget: Element with listener

Tips

  • Use closest() for target matching
  • Always check parent.contains()
  • Use data attributes for action routing