Do not attach listeners for DOMContentLoaded after it already fired

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the javascript category.

Last Updated: 2021-05-15

I had the following code that was supposed to attach the PayPal button for payments, but didn't work:

function attachPayments() {

  document.addEventListener("DOMContentLoaded", attachPaypalButton)

document.addEventListener("DOMContentLoaded", attachPayments)

The issue was that DOMContentLoaded only ever fires once, and it had already fired by the time the code reached the inside of the attachPayments function and executed the part that attached another DOMContentLoaded event for attachPaypalButton.

You can confirm this with a simpler example: Open up Chrome Tools for a web page that has already loaded and enter the following into the JavaScript console:

document.addEventListener("DOMContentLoaded", () => console.log("fired")) 

You will not see "fired" logged - i.e. adding this listener does nothing if the event has already been fired. (You might imagine it would execute the code immediately because the DOMContentLoaded has already fired - but this does not seem to be the behavior).

What to do instead?

I could trigger the code not on global DOM initialization, but rather on the loading of the specific script element I was interested in:

// Attach script is a custom function that returns a reference to a script tag
  // We attach the custom code to this script tag's `onload`
  onload = () => attachPaypalButton(finalizeUrl)

How to be debug these situations in future?

  1. Look at document.readyState. If it says "interactive" then the DOMContentLoaded event has already fired. It could be either loading or interactive or complete. Thus your code should check for both if (document.readyState != "loading")

  2. In the Chrome Dev Tools console you can set breakpoints on events - e.g. Event Listener Breakpoints