JavaScript Reusable Event Delegation Function


Event delegation is a powerful JavaScript technique that allows you to handle events for multiple child elements using a single parent event listener. Instead of attaching event listeners to each individual element, you delegate the event handling to a common ancestor.

This approach improves performance, reduces memory usage, and works perfectly with dynamically added elements. In this guide, you will learn how to create a reusable JavaScript delegate function and how to use it in real-world scenarios.

What Is Event Delegation in JavaScript?

Event delegation is a JavaScript technique where a single event listener is attached to a parent element to handle events for its child elements. Instead of adding listeners to multiple elements, the event bubbles up to the parent, where it is processed based on the event target.

Why Use Event Delegation?

Why Event Delegation Is Important

  • Handles dynamic elements added after page load
  • Improves performance by using fewer event listeners
  • Keeps code clean, reusable, and maintainable
  • Ideal for lists, tables, menus, and large DOM structures

Reusable JavaScript Delegate Function

Below is a reusable function that listens for an event on a parent element and triggers the callback only when the target matches a selector.

JavaScript
function delegate(parent, eventType, selector, handler) {
    parent.addEventListener(eventType, function (event) {
        const targetElement = event.target.closest(selector);
        if (targetElement && parent.contains(targetElement)) {
            handler.call(targetElement, event);
        }
    });
}

How the Delegate Function Works

How It Works

  • parent - The container element
  • eventType - Event name (click, input, change, etc.)
  • selector - Target child selector ('.edit', 'span', '.remove')
  • handler - Callback function executed on match

The closest() method ensures the event works even when clicking nested elements.

Basic Usage Example

HTML
<ul id="menu">
    <li class="item">Home</li>
    <li class="item">About</li>
    <li class="item">Contact</li>
</ul>
JavaScript
const menu = document.getElementById("menu");

delegate(menu, "click", ".item", function (event) {
    alert("Clicked: " + this.textContent);
});

Working With Dynamic Elements

Event Delegation for Dynamically Added Elements

Demo:

  • Click me 1
  • Click me 2
  • Click me 3

In the demo above, the delegate function handles all list item events efficiently. You can append as many new child items to the list as you want without adding separate event listeners for each item. A single delegated event listener attached to the parent element manages events for all current and future child elements.

HTML
<button id="add">Add Item</button>
<ul id="list"></ul>
JavaScript
const list = document.getElementById("list");
const addBtn = document.getElementById("add");

delegate(list, "click", "li", function () {
    alert("You clicked: " + this.textContent);
});

addBtn.addEventListener("click", () => {
    const li = document.createElement("li");
    li.textContent = "Click me " + (list.children.length + 1);
    list.appendChild(li);
});

Delegate One (One-Time Event Delegation)

Sometimes you only want an event to run once, even when using event delegation. A delegate one pattern ensures the handler is executed a single time for the matched element and is automatically removed afterward.

This is useful for:

  • One-time confirmations
  • Intro tooltips
  • Initial user interactions
  • Performance sensitive actions

Delegate One Example

JavaScript
function delegateOne(parent, eventType, selector, handler) {
    const listener = function (event) {
        const target = event.target.closest(selector);
        if (target && parent.contains(target)) {
            handler.call(target, event);
            parent.removeEventListener(eventType, listener);
        }
    };
    parent.addEventListener(eventType, listener);
}

Usage Example:

HTML
<ul id="list-content">
    <li>Click Me Once</li>
    <li>Click Me Once</li>
    <li>Click Me Once</li>
</ul>
JavaScript
const container = document.getElementById("list-content");

delegateOne(container, "click", "li", function () {
    alert("This will run only once!");
});

How It Works

  • The event is delegated to the parent element
  • The handler runs when the selector matches
  • After the first execution, the event listener is removed
  • No additional cleanup is required

Multiple Selectors Support

In some cases, you may want to handle events for different types of child elements using a single delegated event listener. Supporting multiple selectors allows one delegate function to match and respond to more than one target element.

This approach keeps your code concise and avoids duplicating event listeners for similar behaviors.

Delegate With Multiple Selectors Example

JavaScript
function delegateMultiple(parent, eventType, selectors, handler) {
    parent.addEventListener(eventType, function (event) {
        const target = event.target.closest(selectors);
        if (target && parent.contains(target)) {
            handler.call(target, event);
        }
    });
}

Usage Example:

HTML
<ul id="actions">
    <li class="edit">Edit</li>
    <li class="delete">Delete</li>
    <li class="share">Share</li>
</ul>
JavaScript
const actions = document.getElementById("actions");

delegateMultiple(actions, "click", ".edit, .delete, .share", function () {
    alert("Action clicked: " + this.className);
});

How It Works

  • Accepts multiple CSS selectors separated by commas
  • Uses closest() to find the nearest matching element
  • A single event listener handles all matched selectors
  • Works seamlessly with dynamically added elements

When to Use Multiple Selectors

  • Menus with different action items
  • Toolbars and action lists
  • Tables with multiple clickable controls
  • Reducing duplicate delegation logic

Removing Delegated Event Listeners

Sometimes you may need to remove a delegated event listener to prevent further handling, for example when a component is destroyed or when a one-time action is complete. Unlike normal event listeners, delegated listeners require storing the handler reference so it can be removed later.

Removing a Delegated Event Listener

JavaScript
function removingDelegate(parent, eventType, selector, handler) {
    const listener = function (event) {
        const target = event.target.closest(selector);
        if (target && parent.contains(target)) {
            handler.call(target, event);
        }
    };
    parent.addEventListener(eventType, listener);

    // cleanup
    return () => parent.removeEventListener(eventType, listener);
}

Usage:

HTML
<ul id="list">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
</ul>

<button id="remove-listener">Remove listener</button>

JavaScript
const list = document.getElementById("list");

const removeClick = removingDelegate(list, "click", "li", function () {
    alert("Item clicked!");
});

// remove listener by button click
const removeBtn = document.getElementById("remove-listener");
removeBtn.addEventListener("click", () => {
	removeClick();
});

// OR
// Remove the delegated listener after 10 seconds
setTimeout(() => {
    removeClick();
    console.log("Delegated event listener removed");
}, 10000);

How It Works

  • The delegate function returns the actual listener function.
  • The same listener reference is stored and used for removal.
  • removeEventListener() works because it receives that exact function reference.
  • Once removed, existing and newly added child elements no longer trigger the handler.

When to Use

  • Cleaning up listeners when elements are removed from the DOM
  • Preventing memory leaks in long-lived applications
  • One-time actions that should no longer respond after execution

Per-Selector Callbacks

In some situations, different child elements require different behaviors even though they share the same parent and event type. Per-selector callbacks allow you to map each CSS selector to its own handler function while still using a single delegated event listener.

Per-Selector Callbacks Example

JavaScript
function delegateCallbacks(parent, eventType, callbacks) {
    parent.addEventListener(eventType, function (event) {
        for (const selector in callbacks) {
            const target = event.target.closest(selector);
            if (target && parent.contains(target)) {
                callbacks[selector].call(target, event);
                break;
            }
        }
    });
}

Usage:

HTML
<ul id="actions">
    <li class="edit">Edit</li>
    <li class="delete">Delete</li>
    <li class="share">Share</li>
</ul>
JavaScript
const actions = document.getElementById("actions");

delegateCallbacks(actions, "click", {
    ".edit": function () {
    	alert("Edit action");
        console.log("Edit action");
    },
    ".delete": function () {
    	alert("Delete action");
        console.log("Delete action");
    },
    ".share": function () {
    	alert("Share action");
        console.log("Share action");
    }
});

When to Use Per-Selector Callbacks

  • Different actions for different elements
  • Cleaner logic than large if / switch blocks
  • Single delegated listener with clear responsibilities

Event Delegation Best Practices, Performance Benefits, and Common Mistakes

When to Use Event Delegation (Best Practices)

Event delegation is best used when you need to handle events for multiple child elements efficiently, especially when those elements are created dynamically. It is ideal for lists, tables, menus, and components where adding individual event listeners would be repetitive or costly. Attaching a single listener to a parent keeps your code cleaner and easier to maintain.

Event Delegation vs Direct Event Binding

Direct event binding attaches an event listener to each individual element, while event delegation relies on a single listener attached to a parent element. Delegation scales better for large or dynamic DOM structures and reduces memory usage. Direct binding is more suitable for simple, static elements with limited interaction.

Common Mistakes with Event Delegation

Common mistakes include attaching the delegated listener too high in the DOM, using overly broad selectors, or forgetting to verify that the matched element belongs to the intended parent. Another frequent issue is relying on event.target directly instead of using methods like closest() for reliable matching.

❌ Mistake: Using event.target directly

This fails when clicking nested elements.

HTML
parent.addEventListener("click", function (event) {
    if (event.target.matches(".item")) {
        console.log("Clicked item");
    }
});

If .item contains inner elements (like <span> or <strong>), the click will not match.