Archives

MY APPROACH

The right solution, not the biggest solution.

You don't need to know whether your problem needs an API, an automation workflow, a dashboard or a custom application.

Tell me the problem. I'll help figure out a practical way to solve it.

Event Delegation to Handle Dynamic Lists Efficiently

Imagine a product list where users can add new items without reloading the page. You attach a click listener to every existing item, and everything works—until a newly added item does nothing.

The problem is that the new element did not exist when the listener was attached. Instead of repeatedly binding listeners, use JavaScript event delegation: attach one listener to a stable parent and handle events from its children.

What Is JavaScript Event Delegation?

Event delegation relies on event bubbling. When an event occurs on an element, it can bubble up through its ancestors.

For example, when a user clicks a button inside a list item, the click event can travel from the button to the list item and then to the list container.

That means we can listen on the parent rather than attaching a separate listener to every button.

Event Delegation with a Dynamic List

Consider this HTML:

<ul id="product-list">
<li>
    <button type="button" data-product-id="101">
        Remove Product 101
    </button>
</li>
<li>
    <button type="button" data-product-id="102">
        Remove Product 102
    </button>
</li>
<button type="button" id="add-product">
  Add Product
</button>

Instead of attaching a listener to each remove button, attach one listener to the list:

const productList = document.querySelector("#product-list");

productList.addEventListener("click", (event) => {
    const removeButton = event.target.closest(
    "button[data-product-id]"
  );

  if (!removeButton || !productList.contains(removeButton)) {
    return;
  }
  const productId = removeButton.dataset.productId;
  removeButton.closest("li")?.remove();
  console.log(`Removed product: ${productId}`);
});

Now the same listener can handle both existing and newly inserted buttons.

const newItem = document.createElement("li");
newItem.innerHTML = `
    <button type="button" data-product-id="103">
        Remove Product 103
    </button>`;

productList.append(newItem);

No additional event listener is required. The click bubbles to #product-list, where the delegated handler processes it.

event.target vs event.currentTarget

These two properties are easy to confuse:

  • event.target: The element where the event originally occurred.
  • event.currentTarget: The element whose event listener is currently executing.

In the delegated example:

productList.addEventListener("click", (event) => {
  console.log(event.target);        // The clicked button or nested element
  console.log(event.currentTarget); // The #product-list element
});

Using closest() is often safer than checking only event.target.matches(). A button may contain an icon or a <span>, and the user might click that nested element instead of the button itself.

Why Event Delegation Is Useful

  • Dynamic elements work automatically: Newly added children use the existing parent listener.
  • Less listener management: One listener can replace hundreds of individual listeners.
  • Simpler cleanup: Removing child elements does not require removing their individual handlers.
  • Centralized behavior: Related actions can be handled in one predictable location.

This does not mean delegation is always faster in every situation. The main benefit is simpler event management and better support for dynamic interfaces.

Practical Event Delegation Gotchas

1. Use a stable parent

Attach the listener to an element that exists for the lifetime of the interface. If the parent itself is frequently replaced, the listener may disappear with it.

2. Filter events carefully

A parent can receive many bubbled events. Always verify that the event came from the intended control. Methods such as matches(), closest(), and containment checks help prevent accidental actions.

3. Know when bubbling does not apply

Most common events, including click, bubble. Some events, such as focus and blur, do not bubble in the usual way. For those cases, consider bubbling alternatives such as focusin and focusout, or use capture-phase listeners where appropriate.

When a page contains dynamic lists, tables, menus, or repeated controls, event delegation is often the cleanest solution. Rather than chasing newly created elements with more listeners, let the event bubble to a stable parent and handle the interaction in one place.

 

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment

RDA
Rashid Anwar
Independent Developer

I’m Rashid, an independent developer focused on practical software solutions for businesses.

HAVE A PROJECT IN MIND?

Let's talk about the problem you're trying to solve.

Share what you need. We can discuss the requirements and a practical next step.

Start a Conversation