0

I have an tag that in some point in my JS application gets a click eventListener. And unexpectedly gets a second click event listener further in time. I expect the first one to be removed first, before it is added again.

How can i use the chrome dev tools to determine when an event listener is added, removed, and which code caused it, including a call stack?

I tried console logging stuff but i still cannot see the cause.

The adding and removing of listeners is done in such functions

class SomeClass {
    /**
     * Enables listeners so that clicking on the navigatableElement can work.
     */
    enableListeners() {
        this.disableListeners();
        this._navigatableElement.addEventListener('click', this._clickEventHandler.bind(this));
    }

    /**
     * Disables listeners so that clicking on the navigatableElement will never trigger the confirmation modal anymore via this controller
     */
    disableListeners() {
        this._navigatableElement.removeEventListener('click', this._clickEventHandler.bind(this));
    }
}
Jules
  • 97
  • 1
  • 8

2 Answers2

1

Your removal code does not work, because every call to .bind() generates a new function object. You're asking to remove a listener the browser does not see as registered and it ignores the call.

Slawomir Chodnicki
  • 1,525
  • 11
  • 16
1

One possible method that can be used for debugging (but not in production code) is to write to Element.prototype.addEventListener, allowing you to log and console.trace whenever it's called:

const { addEventListener, removeEventListener } = EventTarget.prototype;
Element.prototype.addEventListener = function(...args) {
  console.log('Adding event listener!', this, args);
  console.trace();
  // debugger
  return addEventListener.apply(this, args);
};
Element.prototype.removeEventListener = function(...args) {
  console.log('Removing event listener!', this, args);
  console.trace();
  // debugger
  return removeEventListener.apply(this, args);
};


console.log('real script start');
const fn = () => console.log('fn');
foo.addEventListener('click', fn);
console.log('listener added.');
foo.removeEventListener('click', fn);
<div id="foo">
</div>

Your particular code is not working because every time you .bind, you're creating a new function. You can use the common React pattern of binding in the constructor, that way every reference to this._clickEventHandler will be to the same function. (removeEventListener needs to be called with the exact same function reference addEventListener was called with to work.)

class SomeClass {
  constructor() {
    this._clickEventHandler = this._clickEventHandler.bind(this);
  }
    /**
     * Enables listeners so that clicking on the navigatableElement can work.
     */
    enableListeners() {
        this.disableListeners();
        this._navigatableElement.addEventListener('click', this._clickEventHandler);
    }

    /**
     * Disables listeners so that clicking on the navigatableElement will never trigger the confirmation modal anymore via this controller
     */
    disableListeners() {
        this._navigatableElement.removeEventListener('click', this._clickEventHandler);
    }
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Well, almost there. I did not know about that bind did create a new function! How bad :). But...something strange is still left. Inside the _clickhandler "this" is not the class, but the element i clicked on. What's happening there? I expect it do be the class instance – Jules May 31 '19 at 09:40
  • That's strange - it *should* be the global object, or `undefined`. The `this` calling context gets lost when you pass `this.fn`, just like `fn = this.fn; fn();` calls `fn` without the calling context. See https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback – CertainPerformance May 31 '19 at 09:45
  • Never mind. I did the react like binding trick AFTER adding the listeners. That was a mistake obviously :). Your answer was correct! – Jules May 31 '19 at 09:56