javascriptremoveeventlistener

Javascript event listener not being removed


I'm having an issue were the event listener is not being removed, each time the gameLoop func is called again its doing eveything 2x, 4x, ect.. every time the game loop rolls around.

function gameLoop(data) {
    let currentQuestion = newQuestion(data); // randomizes the question

    displayAnswers.forEach(element => { // removes the added classes to show if the answer is right or wrong
        element.classList.remove('correct');
        element.classList.remove('incorrect');
        element.removeEventListener('click', handleAnswerClick); // remove the event listener IS NOT WORKING
    });

    renderUi(currentQuestion); //loads Q/A to screen

    displayAnswers.forEach(element => { 
        element.addEventListener('click', handleAnswerClick);
    });

    function handleAnswerClick(event) {
        checkAnswer(event, currentQuestion, data);
    }
}

https://drive.google.com/drive/folders/1xOrqXAdbzW0Xsr1nplK0hzcGJQrLbPog?usp=sharing - here is the code with a video of the bug in action


Solution

  • You defined handleAnswerClick in the local scope of gameLoop. The reference to the function is not available when a second call is made to gameLoop. Since handleAnswerClick is defined with the function statement, it is accessible directly from the start of the gameLoop function, and it is then incorrectly passed to removeEventListener.

    The issue happens somewhat like this:

    gameLoop()[run 1]:
    - function handleAnswerClick(...){...}//Even if defined at the end, 
    //functions defined with the function keyword 
    //are available inside the scope they were defined in [here:gameLoop() local scope] at all times
    - addEventListener(...)
    gameLoop()[run 2]:
    - removeEventListener(...)//This is where the issue is.
    //In the second call, a new handleAnswerClick is defined into the local scope,
    //this is a different local scope than the first one.
    //now, the new handleAnswerClick, which is different than the old handleAnswerClick,
    //is passed to removeEventListener. removeEventListener does not throw an error if a 
    //unregistered function is passed, it ignores it instead. This applies to the new function, 
    //as the old function is the one that was registered, and the new function is different from it
    - ...
    gameLoop()[run X]:
    - ...
    

    your new function should look like this:

    function gameLoop(data) {
        let currentQuestion = newQuestion(data); // randomizes the question
    
        displayAnswers.forEach(element => { // removes the added classes to show if the answer is right or wrong
            element.classList.remove('correct');
            element.classList.remove('incorrect');
            element.removeEventListener('click', window.lastClickHandlerFunction); //will be undefined on first call, later it is the correct function
        });
    
        renderUi(currentQuestion); //loads Q/A to screen
    
        displayAnswers.forEach(element => { 
            element.addEventListener('click', handleAnswerClick);
        });
        window.lastClickHandlerFunction=handleAnswerClick;//Store a reference to the registered function
        function handleAnswerClick(event) {
            checkAnswer(event, currentQuestion, data);
        }
    }