2

I have a button subscribe that should submit a post request via ajax to my controller for insertion to my table.

This is how my view look like:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8">
            <div class="flash-message"></div>
            <div class="card">
                <div class="card-header">
                    <div class="level">
                        <span class="flex"><a href="{{route('profile',$thread->creator->name)}}">{{$thread->creator->name}}</a> posted:
                            {{$thread->title}}
                        </span>
                        @if(auth()->check())
                        @if($subscription)
                        <button class="btn btn-secondary" id="unsubscribe">Unsubscribe</button>
                        @else
                        <button class="btn btn-danger" id="subscribe">Subscribe</button>
                        @endif
                        @endif
                        @can('update',$thread)
                        <a href="{{$thread->path()}}/edit" class="btn btn-link">Edit Thread</a>
                        <form action="{{$thread->path()}}" method="POST">
                            @csrf
                            @method('delete')
                            <button class="btn btn-link" type="submit">Delete Thread</button>
                        </form>
                        @endcan
                    </div>
                </div>
                <div class="card-body">
                {{$thread->body}}
                </div>
            </div>

..............

My app.blade:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!--jQuery/share.js -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>
    <script src="{{ asset('js/share.js') }}"></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    <style>
        body{
            padding-bottom:100px;
        }
        .level{
            display: flex;
            align-items: center;
        }
        .flex{
            flex: 1;
        }
    </style>
</head>
<body>
      <div id="app">
        @include('layouts.nav')
        <main class="py-4">
            @yield('content')
        </main>
    <flash message="{{session('flash')}}"></flash>
    </div>
</body>

<style>
    .btn-width{
        min-width: 70px;
      }
</style>


</html>

The code calling the button:

<script type="application/javascript">

    $(document).ready(function(){
        $('#subscribe').click(function(e){
            e.preventDefault();
            $.ajaxSetup({
                  headers: {
                      'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                  }
              });
              
            $.ajax({
                url: "{{route('subscription.store')}}",
                method:'POST',
                data: {
                    thread_id: "{{$thread->id}}",
                },
                success:function(response){
                    $('div.flash-message').html(response);
                   
                },
                error:function(error){
                    console.log(error);
                }
            });
        });
    });
From what I could tell, there is no other element that shares the same id as my button. And, my button is not in a form submit so it should not be called twice. Inspecting dev tools shows no error and in the network tab, two requests are called identically with the same initiator.

So, I am kinda wondering why would this happen. Shouldn't an ajax post request submit the request once only?

I would really like to get to the bottom of this as most of the other similar issues have calling the submit twice while my code is only supposed to call it once. Instead, it makes two insertion to my db.

What else can I do to figure out the root cause of the issue?

4 Answers4

1

Is it possible that your javascript is being loaded twice somehow? That would attach two identical listeners and send the request twice on a single click. If you put a console.log inside of the event handler, do you see that twice as well?

Also, apparently, .click adds a separate event listener for each element that matches the selector passed to the jQuery object, whereas .on only adds a single one.. What would happen if you did this instead?

$(document).ready(function () {
  $("#subscribe").on("click", function(e) {
    e.preventDefault();
    $.ajaxSetup({
      headers: {
        "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
      },
    });

    $.ajax({
      url: "{{route('subscription.store')}}",
      method: "POST",
      data: {
        thread_id: "{{$thread->id}}",
      },
      success: function (response) {
        $("div.flash-message").html(response);
      },
      error: function (error) {
        console.log(error);
      },
    });
  });
});
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/229421/discussion-on-answer-by-dakota-lee-martinez-how-to-prevent-ajax-post-request-fro). – Samuel Liew Mar 03 '21 at 00:53
1

You can try these options:

(1) Use async: false in your ajax call to stop the execution of other code until you receive response of the current ajax call.

 $('#subscribe').click(function(e) {
    e.preventDefault();
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    $.ajax({
        url: "{{route('subscription.store')}}",
        method: 'POST',
        async: false,
        data: {
            thread_id: "{{$thread->id}}",
        },
        success: function(response) {
            $('div.flash-message').html(response);

        },
        error: function(error) {
            console.log(error);
        }
    });
});

OR

(2) You can use stopPropagation() method of the Event interface which prevents further propagation of the current event in the capturing and bubbling phases.

$('#subscribe').click(function(e) {
    e.preventDefault();
    e.stopPropagation();
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    $.ajax({
        url: "{{route('subscription.store')}}",
        method: 'POST',
        async: false,
        data: {
            thread_id: "{{$thread->id}}",
        },
        success: function(response) {
            $('div.flash-message').html(response);

        },
        error: function(error) {
            console.log(error);
        }
    });
});

OR

(3) Use a variable that stores the status of the request.

var isLoading = false;
$(document).ready(function() {
    $('#subscribe').click(function(e) {
        if (!isLoading ) { 
            isLoading = true; //make true when request starts
            e.preventDefault();
            $.ajaxSetup({
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            });

            $.ajax({
                url: "{{route('subscription.store')}}",
                method: 'POST',
                data: {
                    thread_id: "{{$thread->id}}",
                },
                success: function(response) {
                    $('div.flash-message').html(response);
                    isLoading = false; //make false when response is received
                },
                error: function(error) {
                    console.log(error);
                    isLoading = false; //make false when error is received
                }
            });
        }
    });
});
TRK
  • 188
  • 1
  • 7
0

Have you tried giving return false? like this:

$(document).ready(function(){
    let subscribeClick = function() {
     $.ajaxSetup({
              headers: {
                  'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
              }
          });
          
        $.ajax({
            url: "{{route('subscription.store')}}",
            method:'POST',
            data: {
                thread_id: "{{$thread->id}}",
            },
            success:function(response){
                $('div.flash-message').html(response);
               
            },
            error:function(error){
                console.log(error);
            }
        });
        return false;
    }
    $('#subscribe').click(function(e){
        e.preventDefault();
        e.stopImmediatePropagation();
        subscribeClick(); 
    });
});
glovemobile
  • 302
  • 2
  • 8
  • If I add the stopImmediatePropagation just after event preventDefault then it works. However, I am just trying to understand what is the cause of the issue seeing it should work by default. –  Mar 02 '21 at 06:27
  • - preventDefault: Cancels the event if it is cancelable, without stopping further propagation of the event. - stopPropagation: Prevents further propagation of the current event. - stopImmediatePropagation: Prevents other listeners of the same event from being called. – glovemobile Mar 02 '21 at 06:29
  • Do you know why it's happening ? I've use a lot of POST ajax request and never needed to use stopPropagation. –  Mar 02 '21 at 06:30
  • I don't really understand your case, but maybe this link will give you better understanding https://itnext.io/preventdefault-vs-stoppropagation-3631de9fe1c6 – glovemobile Mar 02 '21 at 06:38
  • I meant to say my post request via ajax should work as normal but in my case it's not working and trying to find out why. –  Mar 02 '21 at 06:45
  • maybe you have multiple selectors with same name? or there's another script that force to load the page twice? kindly check just to make sure. – glovemobile Mar 02 '21 at 06:52
  • this is the only element with id = subscribe. As for another script that force to load the page twice, which element should i check? –  Mar 02 '21 at 06:55
-1

you are calling you function twice one in document ready and second on button click remover document

Ali Hmaza
  • 1
  • 4