Search code examples
javascriptruby-on-railsajaxshopping-cart

Rails is creating multiple shopping cart records per click


I am following this guide for creating a shopping cart model: https://richonrails.com/articles/building-a-shopping-cart-in-ruby-on-rails

I got it to work successfully, but still have a problem. When I load the page, and add an item, one is added, if I go to another page, and then load the home page again, through a side bar menu I have, I click a product, and that same product gets added 3 times to the shopping cart. I go to another page and return, 5 items per click, again 7 items per click. I have no idea why this is happening, I don't even know what part of my code to show, so someone can help me. If I reload the page (by clicking the address bar and enter), it goes back to adding one item per click.

Thanks in advance

EDIT: After first comment suggestion, here is the controller code.

def create
    @invoice = current_invoice
    @invoice_product = @invoice.invoice_products.new(invoice_product_params)
    @invoice.save
    session[:invoice_id] = @invoice.id
end

def update
    @invoice = current_invoice 
    @invoice_product = @invoice.invoice_products.find(params[:id])
    @invoice_product.update.attributes(order_item_params)
    @invoice_products = @invoice.invoice_products
end

def destroy
    @invoice = current_invoice 
    @invoice_product = @invoice.invoice_products.find(params[:id])
    @invoice_product.destroy
    @invoice_products = @invoice.invoice_products
end

private
def invoice_product_params
    params.require(:invoice_product).permit(:id, :invoice_id, :product_id, :price, :tax, :value)
end

Solution

  • that same product gets added 3 times to the shopping cart. I go to another page and return, 5 items per click, again 7 items per click

    This has all the hallmarks of Turbolinks and bad JS binding.

    --

    Let me explain...

    Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body (or parts of) and the title in the head. Think CGI vs persistent process.

    In short, Turbolinks uses "Ajax" to load the <body> of the next page, replacing your current <body> content. Whilst this does speed up processing (by removing the need to recompile CSS/images), it causes havoc with JS bindings.

    JS "binds" to elements in the DOM:

    enter image description here

    It expects there to be elements for it to bind to. This works very well in most cases:

    element = document.getElementById("your_element").addEventListener('click', function() {
       console.log('anchor');
    });
    

    However, the problem with using Turbolinks (and especially JQuery) is the binding can occur multiple times depending on how many times Turbolinks just loads new data into the DOM.

    The issue is because your Javascript is not refreshing, but your DOM elements are, JS is treating them like new elements, thus triggering the function x number of times with each click. Kind of like n+1 I suppose.

    --

    In answer to your problem, the issue will lie with your JS bindings:

    #app/assets/javascripts/application.js
    bind = function() {
        $("#shopping_cart").on("click", function(){
            //payload
        });
    };
    $(document).on("ready page:load", bind);
    

    The above will you the "local" selection for the elements, and using the page:load Turbolinks hook, will make sure it refreshes each time Turbolinks is requested.

    If you wanted to do it without having to redeclare each time Turbolinks is called, just delegate from the document:

    #app/assets/javascripts/application.js
    $(document).on("click", "#shopping_cart", function(){
       //payload
    });