Prevent jQuery events from firing multiple times

Written by on June 17, 2019

Prevent jQuery events from firing multiple times

For some reason, even after five years, this article is still actively visited and it makes me deeply surprised.

This issue, like many other jQuery based issues, is not an error on its own. It is simply how jQuery (and Javascript) works and we need to deal with it as best as we can. I will offer you solutions that helped me in the past 6 years; though I must admit I no longer use jQuery or jQuery Mobile.

One more thing before we start. This article is written from the perspective of jQuery and jQuery Mobile as this is the case where this issue becomes the most visible. Thankfully three out of four solutions will work with jQuery, just remove jQuery Mobile page events and what’s left will work just fine with jQuery alone.

Note: If this tutorial was helpful, you need further clarification, something is not working or you have a request for another Ionic post? Furthermore, leave me a comment below if you don't like something about this blog, if something is bugging you, don't like how I'm doing stuff here. Feel free to comment below, subscribe to my blog, mail me to dragan.gaic@gmail.com. Thanks and have a nice day!

Table of Contents

Introduction

In recent years, jQuery developers gave us several event binding functions:

Update: This page used to have embedded working examples, unfortunately, iframes heavily prolonged page loading so I moved the here. Or you can access them directly via below examples.

None of these functions will check if the given event is already bound.

Now comes the funny part, jQuery Mobile works in a different way than classic web applications. Depending on your page structure and logic, each time you visit certain pages it will bind those events over and over again.

As previously stated, this is not an error; it is simply how jQuery Mobile handles page events. For example, take a look at this code snippet:

$(document).on('pagebeforeshow','#index' ,function(e,data){    
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    });    
});

Each time you visit page #index, click event will be bound to button #test-button. You can test this problem in the example below, simply move from page 1 to page 2 and back several times in a row.

Error Example

To my knowledge, there are four solutions to this issue. There may be more, however, I never had a reason to search for more.

Before we continue, if you are here from the jQuery Mobile point of view, make sure you know how and when to bind events when working on jQuery Mobile application. It’s a crucial step, first avoid using document ready when working with jQuery Mobile. Why? As application initializes for the first time, jQuery Mobile requires some time to process initial page markup, and document ready will usually trigger before this process can end. If document ready triggers before page markup is ready there’s a good chance it will try to access HTML elements not yet present in the DOM.

Solution 1

Info: jQuery Mobile only solution.

The easiest solution would be to use pageinit for event binding. Replace pageinit with pagecreate if you are using jQuery Mobile 1.5 as pageinit was deprecated since version 1.4.

As stated in the jQuery Mobile documentation, pageinit will trigger only once, just like document ready, so multiple bindings will never occur. Unfortunately, there’s one little thing not mentioned in the official documentation.

Pageinit will trigger only once if used in multipage template, if multi-HTML is used pageinit will trigger every time page is loaded into the DOM (more information can be found here and here).

$(document).on('pageinit', '#index', function(){       
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
Working Example 1

Here’s a code example if jsFiffle is unavailable:

HTML

<!DOCTYPE html>
<html>
<head>
    <title>jQM Complex Demo</title>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=no; target-densityDpi=device-dpi"/>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
    <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>    
</head>
<body>
    <div data-role="page" id="index">
        <div data-theme="a" data-role="header">
            <h3>
                First Page
            </h3>
            <a href="#second" class="ui-btn-right">Next</a>
        </div>

        <div data-role="content">
            <a data-role="button" id="test-button">Click me</a>
        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div> 
    <div data-role="page" id="second">
        <div data-theme="a" data-role="header">
            <h3>
                Second Page
            </h3>
            <a href="#index" class="ui-btn-left">Back</a>
        </div>

        <div data-role="content">

        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div>    
</body>
</html>   

JavaScript

$(document).on('pageinit', '#index', function(){       
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});

Solution 2

Info: jQuery Mobile and/or jQuery solution. For jQuery only solution just remove pagebeforeshow from the below example.

The second solution is another easy approach. Before we bind an event, we should try to unbind it first. Every function used for event binding has a similar function used to remove related event (you will find them enumerated at the beginning of this article, every binding function comes with a related removal function). Use this solution if the first one is not suitable, mostly because it’s more complicated and demanding.

$(document).on('pagebeforeshow', '#index', function(){       
    $(document).off('click', '#test-button').on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
Working Example 2

Here’s a code example if jsFiffle is unavailable:

HTML

<!DOCTYPE html>
<html>
<head>
    <title>jQM Complex Demo</title>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=no; target-densityDpi=device-dpi"/>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
    <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>    
</head>
<body>
    <div data-role="page" id="index">
        <div data-theme="a" data-role="header">
            <h3>
                First Page
            </h3>
            <a href="#second" class="ui-btn-right">Next</a>
        </div>

        <div data-role="content">
            <a data-role="button" id="test-button">Click me</a>
        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div> 
    <div data-role="page" id="second">
        <div data-theme="a" data-role="header">
            <h3>
                Second Page
            </h3>
            <a href="#index" class="ui-btn-left">Back</a>
        </div>

        <div data-role="content">

        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div>    
</body>
</html>   

JavaScript

$(document).on('pagebeforeshow', '#index', function(){       
    $(document).off('click', '#test-button').on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
On the next page you will find three aditional solutions and working examples so keep reading

Categories

44 thoughts on “Prevent jQuery events from firing multiple times”

  1. Wow.. nice one.

    I’ve had this error before, don’t remember ho I handled it, but I know it was tricky.
    I even tried using the native javascript “onload” to wrap my jquery events, but It still happened anyway.

    This is a really good way to work around it.

    Thanks Dragan.

  2. Thanks for this! I used second solution and it works great! Your post is a fantastic collction of useful solutions.

  3. I used Solution #4 which was perfect for my needs. I couldn’t figure out for the life of me why my submit button within my partial view was executing twice. This solution solved my problem easy. Thanks!

    • off() should be used with on() :

      $(“#button”).off(“click”).on(“click”,function(e){ return false));

      Take from the official jQuery website:

      As of jQuery 1.7, use of .die() (and its complementary method, .live()) is not recommended. Instead, use .off() to remove event handlers bound with .on()

  4. What about event.stopImmediatePropagation()?

    From jquery site:

    Keeps the rest of the handlers from being executed and prevents the event from bubbling up the DOM tree.

    Will this solve the problem?

    BTW very nice explanations!!

  5. This is crazy. I am not even using standard DOM, I am triggering my own: $menu_item.trigger(‘menuclose’) and it still triggers twice. Gonna use e.handled solution. thx

  6. Im usually making pages with a lot of refrehing divs and reloading stuff, so i have made my own simple solution which is handling all of my needs:
    $(‘myclass:not(.initialized)’).on(‘click’, function() { do something }).addClass(‘initialized’)
    then you can call your init functions all the time and not worry about multiple event bindings

  7. While I’m working with the pagecontainer widget in jQuery Mobile 1.4.5, a have a pagebeforeshow event handled this way:

    $(document).on('pagecreate', function(event, ui){

    $(":mobile-pagecontainer").on('pagecontainerbeforeshow', function(event, ui){

    if (ui.toPage[0].id == 'target_specific_page'){

    console.log(ui);

    }

    });

    });

    $(document).on('pagecreate', '#specific_pages', function(){

    //stuff here

    });

    Which works but when I’m about to open the target page, it triggers the “pagecontainerbeforeshow” once per every page that was created before reaching the target page. For example if I had to navigate 4 times to get to the desired page, the console won’t show anything until I’m about to enter the desired page, but once the event triggers it logs 4 ui objects with the same info.

    Dragan Gaić Is there anything wrong with this implementation? The pagecontainer widget usage is still not clear enough for me. If there’s nothing wrong, considering I can’t use the ones that will trigger it only once, which is better between the off.on method and the event filter plugin in terms of performance?

  8. While I’m working with the pagecontainer widget in jQuery Mobile 1.4.5, a have a pagebeforeshow event handled this way:

    $(document).on('pagecreate', function(event, ui){

    $(":mobile-pagecontainer").on('pagecontainerbeforeshow', function(event, ui){

    if (ui.toPage[0].id == 'target_specific_page'){

    console.log(ui);

    }

    });

    });

    $(document).on('pagecreate', '#specific_pages', function(){

    //stuff here

    });

    Which works but when I’m about to open the target page, it triggers the “pagecontainerbeforeshow” once per every page that was created before reaching the target page. For example if I had to navigate 4 times to get to the desired page, the console won’t show anything until I’m about to enter the desired page, but once the event triggers it logs 4 ui objects with the same info.

    @dragangai:disqus Is there anything wrong with this implementation? The pagecontainer widget usage is still not clear enough for me. If there’s nothing wrong, considering I can’t use the ones that will trigger it only once, which is better between the off.on method and the event filter plugin in terms of performance?

    • pagecontainer is still work in progress or at least it was. From what I can see at jQuery Mobile GitHub repository we’re talking about a dead framework. It’s no longer in development, at least since August. My advice, switch back to classic page events. I know they’re deprecated but its’ not like we’re going to see another jQuery Mobile version.

      • oh I knew it was slowing down but I didn’t know it was this bad 🙁 so there won’t be a jQuery Mobile 1.5 or the rumors about the jQuery UI fusion?

        I should also consider to move to another framework from now on; since my main usage was phonegap + jqm, do you consider react native a good option to move in?

        Thanks for the info 🙂

        • If you have a good Phonegap experience you should try Ionic + Cordova or if you still prefer jQuery then go with KenodUI + Cordova.

          On the other hand, React Native is a good choice, but you are somewhat limited. First, you’ll get a real native app but can only choose preexistent built in/3rd party components. You can’t modify them like you would with HTML5 and CSS. Second thing, unless nothing changed last 1-2 months you will be able to use it only on MacOS.

          • React is in rc for Linux and Windows so far as I’ve read but you’re right about the limitations it has, I think I’ll try with Ionic first. Are these performance wise better than jQuery Mobile? Thanks for your advice

          • I’m not a jQuery Mobile hater, but currently any other framework is faster than jQuery Mobile. Especially Ionic and OnsenUI. Though, if you mess up your code, it can became slower 🙂

  9. May the code be with you man, it’s my first true cross platform web application with tons of jquery and this “bug” was driving me crazy. Cheers

  10. God bless you… You saved me from going crazy… If not for your post, i would have spent days to discover a solution.

      • Appreciate that answer. Mind elaborating on any issues you’ve had specifically with using stopImmediatePropagation() on a click event with button siblings as pagination elements?

        • I have an example that happend to me several days ago. I’m no longer using jQuerry but I was traying to fix something on a legacy software. For some reasons, dynamically generated buttons were triggering page refresh. Using stopImmediatePropagation on this event cause it to stop aworking all together.

  11. I’ve a similar problem on my project with jquery… using syntax like $( document ). on (“click”,”.class”, function(){ //dummy()}) each time page (or partial) reloads another handler is registered and event is fired many times. Anyway I’ve notice that using syntax like $(“.class”).click(function(){ something() }) when page or partial reloads only one event is fired. It’s just a stroke of luck or there’s a particular reason?

Leave a Reply