124

I have the following code to add eventListener

 area.addEventListener('click',function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          },true);

It is working correctly as expected..Later in another function i tried to remove the event listener using the following code

 area.removeEventListener('click',function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          },true);

But the even listener is not removed..Why is it happening?Is there any problem with my removeEventListener()? Note:Here area is something like document.getElementById('myId')

Jinu Joseph Daniel
  • 5,864
  • 15
  • 60
  • 90
  • Possible duplicate of [JavaScript: remove event listener](https://stackoverflow.com/questions/4402287/javascript-remove-event-listener) – Heretic Monkey Jun 11 '19 at 14:14

14 Answers14

192

This is because that two anonymous functions are completely different functions. Your removeEventListener's argument is not a reference to the function object that was previously attached.

function foo(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          }
 area.addEventListener('click',foo,true);
 area.removeEventListener('click',foo,true);
duri
  • 14,991
  • 3
  • 44
  • 49
  • 73
    +1 True. `bind(this)` will change the signature. So always assign the function to a `var` after binding `this` to using function `bind` API so that same `var` can be used in `removeListener`. You will see this problem more evident in typescript – NiRUS Mar 28 '17 at 10:33
  • 4
    That won't allow you to pass function parameters f.e. `foo(1)` – IC_ May 05 '17 at 11:19
  • 37
    If someone use classes try something like `this.onClick = this.onClick.bind(this)` before any listener, then `btn.addEventListener('click', this.onClick)`, finally `btn.removeEventListener('click', this.onClick)` – joseluisq May 05 '19 at 22:48
  • @Herrgott To pass arguments to the handler function you can use currying: `foo = (argumentToPass) => (event) => { doSomething(); }`, then `xyz.addEventListener('click', foo('myarg'), true);`. The `foo('myarg')` will return another function with `argumentToPass` set to `myarg`. Just remember in real code to keep hold of a reference to the fn :-) – raven-king Aug 30 '21 at 14:41
  • @joseluisq can you please explain what bind(this) means? it works but i dont know why – Arh Hokagi Oct 19 '21 at 10:13
  • Note that as of late 2020 we don't need removeEventListener to remove an event listener, we can use an abort signal now, which means we don't need to construct a reference to the original function at all, instead we can [use an AbortController](https://stackoverflow.com/a/70498530/740553) – Mike 'Pomax' Kamermans Dec 27 '21 at 17:46
  • @ArhHokagi `.bind(this)` puts the function on a higher-level scope, making `this` becoming the scope of where the function resides. If you use ES6 syntax, this won't be necessary ever. – paddotk Apr 05 '22 at 14:13
28

I find that for the windows object, the last param "true" is required. The remove doesn't work if there is no capture flag.

Slavik
  • 1,053
  • 1
  • 13
  • 26
23

In a React function component, make sure to define the callback with the useCallback(() => {}) hook. If you fail to do this, the callback will be a different one on every re-render and the removeEventListener method will not work.

const scrollCallback = useCallback(() => { // do sth. });
window.addEventListener("scroll", scrollCallback, true);
window.removeEventListener("scroll", scrollCallback, true);
ph1lb4
  • 1,982
  • 17
  • 24
17

It looks like no one's covered the part of the DOM specification (that both browsers and Node.js implement) that now gives you a mechanism to remove your event listener without using removeEventListener.

If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed as options when setting up an event listener:

{
    type (a string)
    callback (an EventListener object, null by default)
    capture (a boolean, false by default)
    passive (a boolean, false by default)
    once (a boolean, false by default)
    signal (an AbortSignal object, null by default)
    removed (a boolean for bookkeeping purposes, false by default)
}

Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us remove an event listener by using an AbortController instead of having to bother with keeping a reference to the exact handler function and listener options "because otherwise removeEventListener won't even work properly":

const areaListener = new AbortController();

area.addEventListener(
  `click`,
  ({clientX: x, clientY: y}) => {
    app.addSpot(x, y);
    app.addFlag = 1;
  },
  { signal: areaListener.signal }
);

And now, when it's time to remove that event listener, we simply run:

areaListener.abort()

And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same funcation and properties as we called addEventListener: we just cancel the listener with a single, argumentless, abort call.

And of course, also note that if we want to do this "because we only want the handler to fire once", then we don't even need to do this, we can just create an event listener with { once: true } and JS will take care of the rest. No removal code required.

area.addEventListener(
  `click`,
  () => app.bootstrapSomething(),
  { once: true }
);
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
8

You are creating two different functions in both calls. So the second function does not relate in any way to the first one and the engine is able to remove the function. Use a common identifier for the function instead.

var handler = function(event) {
              app.addSpot(event.clientX,event.clientY);
              app.addFlag = 1;
          };
area.addEventListener('click', handler,true);

later you can then remove the handler by calling

area.removeEventListener('click', handler,true);
Sirko
  • 72,589
  • 19
  • 149
  • 183
5

To remove it, store the function in a variable or simply use a named function and pass that function to the removeEventListener call:

function areaClicked(event) {
    app.addSpot(event.clientX, event.clientY);
    app.addFlag = 1;
}

area.addEventListener('click', areaClicked, true);
// ...
area.removeEventListener('click', areaClicked, true);
typeoneerror
  • 55,990
  • 32
  • 132
  • 223
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 2
    but how can i pass arguments(here event ) to that function..That's why i used anonymous function – Jinu Joseph Daniel May 04 '12 at 06:58
  • 1
    It is passed by the browser. It doesn't matter if you define the function separately or not. – ThiefMaster May 04 '12 at 06:59
  • 3
    WARNING: I found out what was wrong with my approach. The removeEventListener() method ONLY works with NAMED FUNCTIONS. It does NOT work with anonymous functions! When I edited the code to take this into account, everything worked as planned. You have to define a NAMED function in your closure, and return a reference to an instance thereof with the parameters passed by the closure. Do this, and removeEventListener() works perfectly. – David Edwards Jan 19 '18 at 09:44
2

If you want to pass local variables to the function called by the event listener, you can define the function inside the function (to get the local variables) and pass the name of the function in the function itself. For example, let's start inside the function that adds the event listener with app as a local variable. You would write a function inside this function such as,

function yourFunction () {
    var app;

    function waitListen () {
        waitExecute(app, waitListen);
    }

    area.addEventListener('click', waitListen, true);
}

Then you have what you need to remove it when waitExecute is called.

function waitExecute (app, waitListen) {
    ... // other code
    area.removeEventListener('click', waitListen, true);
}
VectorVortec
  • 667
  • 7
  • 10
  • I've encountered a problem here. Even if you define an event handler function, save a reference to that function, and then pass that reference to removeEventListener() later, the function isn't removed. Comment's too small to post code in, so if you want code, I'll have to use up an answer box ... – David Edwards Jan 18 '18 at 09:49
  • Addendum to the above: another interesting phenomenon I've found, is that even if you specify that your event listener is passive, the old one still persists in the chain. Worse still, the old one now becomes a blocking event handler, whilst the new one keeps its passive status. I think an explanation is needed here. – David Edwards Jan 18 '18 at 09:52
1

define your Event Handler first,

and then

area.addEventListener('click',handler);
area.removeEventListener('click',handler);
neohope
  • 1,822
  • 15
  • 29
  • 1
    For future visotirs: we used to need this, but JS as of 2020 has an abort signal that you can use instead, so [you don't need removeEventListener](https://stackoverflow.com/a/70498530/740553) anymore. – Mike 'Pomax' Kamermans Jul 23 '22 at 00:09
1

This is what I ended up doing but it's in a route class but should not make much difference, I wanted for the event listener not to accumulate each time afterModel() is called but also needed arguments and scope so that the model is changed each time.

export default class iFrameRoute extends Route {

      afterModel(model) {

           this.initFrame = function(event) {  
    
               alert("I am being called");

               window.removeEventListener("message",  this.route.test); 

           }.bind({route: this, data: model});

           window.addEventListener("message",  this.initFrame ); 
       } 
}
Epirocks
  • 480
  • 4
  • 11
1

while adding function store in array and removing pass by map work for me

const [functionObjects, setfunctionObjects] = useState([]);

const addListener = (beforeUnloadListener) =>{ 
    setfunctionObjects([...nano, beforeUnloadListener]);
    addEventListener("beforeunload", beforeUnloadListener, {capture: true});
};

const removeListener = (beforeUnloadListener) => { 
    functionObjects.map((item) => {
    removeEventListener("beforeunload", item, {capture: true});});
};
Saeed Zhiany
  • 2,051
  • 9
  • 30
  • 41
1

I went through this same problem recently. A reasonble solution that I found was remove attribute "onclick" on element from HTMLElement class.

Let's imagine that you already got your component from DOM - using document.getElementById or document.querySelector - you can try that code:

js

const element = document.getElementById("myelement");
element.attributes.removeNamedItem('onclick');

html example

<div onClick="memoryGame.flipCard(this)">
   .... // children elements
</div>

I know which this solution it ins't the best, but it works!

I hope I was able to help you.

Cheers!

PS: please, give me a "useful answer"... thanks :D

1

In case of React we can use useRef() to store our listener function in current property. So that in case of re-render and in case of remove listener it will maintain the same reference to the function.

const handleWindowClick = useRef(() => {
    console.log("window clicked");
  });



// for attaching event listener
 window.addEventListener("click", handleWindowClick.current);
 
 // for detaching event listener
 window.removeEventListener("click", handleWindowClick.current);
manas
  • 6,119
  • 10
  • 45
  • 56
1

Update 2023

  • I was using Angular and after numerous tries using AbortController() nothing solved my problem.
  • Finally renderer2 to the rescue. Here's what I did
mouseMoveListener :any;
mouseUpListener :any;

this.mouseMoveListener = this._renderer.listen("document", "mousemove", (event) => {
        this.onMouseMove(event);
      });

this.mouseUpListener = this._renderer.listen("document", "mouseup", (event) => {
        this.onMouseUp(event);
});

ngOnDestroy(){
 this.mouseMoveListener();
 this.mouseUpListener();
}
HariHaran
  • 3,642
  • 2
  • 16
  • 33
1

I've used global variable in window to store the listener callback.

window.logoutListener = () => {
  // code
};

document.addEventListener('click', window.logoutListener, {
   capture: true,
});

// ------- In other file, out of context --------
document.removeEventListener('click', window.logoutListener);

The reason why removeEventListener() did not work here was because third parameter had to be the same.

So specifying the same signature for event listener options worked out

document.removeEventListener('click', window.logoutListener, {
  capture: true,
});