[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:
- Make sure the addEventListener call is within
DOMContentLoaded
event - Use event delegation
- Check that the element exists
- Attach the event AFTER the AJAX call completes
- Fix for jQuery
- 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 thedocument
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.