[FIXED] addEventListener not working AJAX

Step by step to solve common issues with 'addEventListener' not working with AJAX. For example event delegation!

Jul 15, 2023 | Read time 9 minutes | Updated on Feb 4, 2024

๐Ÿ”” Table of contents

Introduction

Generally addEventListener is not working with your AJAX call could be due to the element not existing yet, its been removed or that you have lost or overwrote the listener function.

Recently I had this issue with one of my web apps and was scratching my head.

My web application was displaying a table with paging.

Now when the user pages to the next set of data, the AJAX call was made - it was wiping out the DOM elements and this will also wipe out any event handlers that was attached to those elements.

In this post I will go over the fixes I have tried to get rid of this problem

To fix this we can do the following:

  1. Make sure the addEventListener call is within DOMContentLoaded event
  2. Use event delegation
  3. Check that the element exists
  4. Attach the event AFTER the AJAX call completes
  5. Fix for jQuery
  6. Make sure that there are no errors with the AJAX call

1. Make sure the addEventListener call is within DOMContentLoaded event

The first thing to make sure is that we are wrapping our code in the DOMContentLoaded. This just ensures that everything should be there when the page loads.

To wrap your code in a DOMContentLoaded event - follow the example:

document.addEventListener('DOMContentLoaded', function() {
  // Your code here
});

Note: DOMContentLoaded

This is a native event that gets triggered when the browser correctly parses HTML. So make sure your addEventListener is within this function so that we know we are dealing with all the DOM objects being loaded already!

2. Use event delegation

This is usually my go to method when dealing with dynamic DOM elements - eg when the AJAX call completes.

Event delegation allows you to attach a single event listener to a parent element that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future.

Now with AJAX calls - where new elements can be added dynamically, event delegation is particularly useful.

To set up event delegation:

document.addEventListener('click', function(e) {
    // If the clicked element doesn't match our selector, bail
    if (!e.target.matches('#your_element_id')) return;

    // Otherwise, run your code...
    console.log('Element was clicked!');
});

With the above code, a click event listener is added to the entire document element.

When the dynamic element is clicked, the listener checks if it matches the selector #your_element_id.

If it does, then the desired action (in this case, logging a message to the console) is performed.

TIP

It is best practice to use a more specific element than document as your event listener if you can, for performance reasons.

For example lets say that the AJAX-loaded elements are being added to a table of class .myTable - we should add the event listener to that table instead of the document element.

3. Check that the element exists

One thing that could trip you up is that the element you are adding the โ€œaddEventListenerโ€ function does not even exist.

With non-AJAX scenarios this occurs when the scripts that run too early - for example being added on the top of the page.

This is because the browser will execute scripts from the top of the document to the bottom!

To get around this you can place your <script> at the end of the HTML page and also use the defer attribute:

<script src="demo_defer.js" defer></script>

This ensures that it is executed AFTER the page has finished parsing!

Now with our AJAX scenarios, we could be generating elements dynamically after the AJAX call completes.

So make sure to use addEventListener after the AJAX call - see option 4 for an example.

Consider checking the element has not been removed

In my scenario, I was loading a table and adding event listeners to each of the <div> rows.

What I did not realize is that I was blowing away all rows when I was paging. This in turn will get rid of the event listeners attached to those elements!

A note on defered scripts

  • Deferred scripts wait for stylesheets to load before executing.
  • The DOMContentLoaded event fires after deferred scripts have executed.
  • Scripts not marked as deferred or asynchronous (e.g., <script>) pause until stylesheets that have been parsed load.

4. Attach the event AFTER the AJAX call completes

Make sure to be attaching the addEventListener after the AJAX call.

As an example:

// Perform the AJAX request
fetch('https://your-website.com/somedata')
  .then(response => response.text())
  .then(data => {
    // build up dynamic content here
    //.. 
    const dynamicElement = document.getElementById('dynamic_element_id');
    if(dynamicElement) { // Check if the element actually exists
        dynamicElement.addEventListener('click', () => {
            console.log('Dynamic element was clicked!');
        });
    } else {
        console.log('Dynamic element not found!');
    }
  })
  .catch((error) => {
    console.error('Error:', error);
  });

In the the above, I am using the fetch call to perform a AJAX request.

Now we want to attach the addEventListener after the fetch was successful in the .then() methods.

TIP

Itโ€™s always a good practice to add a catch to the end of a Promise chain to handle any errors that might occur during the AJAX call.

If you want to support older browsers, and example of attaching addEventListener using XMLHttpRequest:

<!DOCTYPE html>
<html>
<body>

<div id="content"></div>

<script>
var request = new XMLHttpRequest();
request.open("GET", "data.json", true);
request.onreadystatechange = function () {
    if (request.readyState === 4 && request.status === 200) {
        var data = JSON.parse(request.responseText);

        // Attach addEventListener here
    const dynamicElement = document.getElementById('dynamic_element_id');
    if(dynamicElement) { // Check if the element actually exists
        dynamicElement.addEventListener('click', () => {
            console.log('Dynamic element was clicked!');
        });
    } else {
        console.log('Dynamic element not found!');
    }


}
};
request.send();
</script>

</body>
</html>
  • The onreadystatechange property is set to a function that will be called whenever the state of the request changes.
  • The readyState of 4 indicates that the request is complete, and a status of 200 indicates a successful HTTP request.

5. Fix for jQuery

If you are using jQuery, you can use the on function to handle this:

$(document).on('click', '#your_element_id', function() {
  // Your code here
});

Under the hood of the .on function - jQuery also uses event delegation to attach event handlers to dynamic elements.

6. Make sure that there are no errors with the AJAX call

If you find the above options did not work, we can then debug and look at our existing code base. Now this can be more time consuming.

When there is a JavaScript error that runs previously to your code - it will stop the event handler from executing. Check your browserโ€™s console for error messages:

  • press F12 on most browsers to open the Developer Tools, and click on the โ€˜Consoleโ€™ tab.

Some things to look out for in your Javascript codebase:

  • Syntax errors - for example missing semicolons, typos, addEventListener using function call instead of function reference, etc
  • Reference errors - for instance null elements, attaching addEventListener to non-DOM elements.
  • AJAX call errors - the AJAX call you are making is not correct and returning error responses like 401 - Forbidden or 404 not Found

Summary

In this post we looked at why addEventListener is not working after our AJAX call. This comes down to the possible reasons:

  • The element does not exist or that we have removed the element
  • We are not attaching addEventListener after the success of the AJAX call
  • There are other JavaScript errors in the codebase to stop the event handler from executing.

In this scenario, my general go to fix would we using event delegation. I would attach addEventListener to the container element and check for the dynamically loaded child elements.

๐Ÿ‘‹ About the Author

G'day! I am Huy a software engineer based in Australia. I have been creating design-centered software for the last 10 years both professionally and as a passion.

My aim to share what I have learnt with you! (and to help me remember ๐Ÿ˜…)

Follow along on Twitter , GitHub and YouTube