JavaScript Event Listener Works Once, Then Stops , What Am I Missing?

I’m working on a small JavaScript project and ran into a strange problem.

I have a button with a click event. The first click works perfectly, but after updating some HTML on the page, the button stops responding.

Something like this:

const button = document.querySelector('#add');

button.addEventListener('click', () => {
    console.log('Button clicked');
});

document.querySelector('#container').innerHTML = `
    <button id="add">Add</button>
`;

After replacing the HTML, the new button looks exactly the same, but the event listener no longer works.

I know there are a few ways to solve this, including event delegation, but I’m interested in understanding why the original event listener disappears when the DOM is changed.

Is event delegation the best approach here, or would you handle this differently?

Would love to hear how you normally deal with this in real projects.

Because the event listener is attached to the actual button element, not to its ID. When you replace the HTML with innerHTML, the old button is removed and a new one is created. It may look identical and have the same ID, but it’s a different element, so it doesn’t have the old event listener.

Normally when I encounter this in a project, I just use event delegation and move on with my life.

But as you say, there are several ways to solve it. E.g. you could create the element with JavaScript instead of replacing it with innerHTML, avoid replacing the button at all if you only need to change its text or state, or use a framework such as React or Vue, where this kind of thing is handled for you.

I’d use whichever approach is clearest and makes the most sense in the context of the codebase.

That’s odd that the first click works perfectly. In theory it shouldn’t log at all, as the button and it’s listener have been replaced with a button that doesn’t have a listener.

Was the first button inside of the #container element? Is the innerHTML set after a passage of time? e.g. after your first click.

Event delegation sounds like the simpler option and the way to go. This just illustrates one of the other ways e.g. creating the element manually.

function buttonHandler(event) {
    console.log('button clicked');
}

function makeButton(id, text, handler) {
    const button = document.createElement('button')
    button.id = id
    button.textContent = text;
    // eventListener attached to new element
    button.addEventListener('click', handler);
    
    return button
}

// replace the children with the new element
container.replaceChildren(
    makeButton('add', 'click new button', buttonHandler)
)