Prevent unnecessary clicks with Vue.js

In my vue.js application, there is a feature to remove items.

The following code snippet shows the div element:

 <div class="ride-delete" @click="delete">
      <p>Delete</p>
 </div>

This is the function used to handle the click event:

methods: {
    delete ()
    {
      swal({
        title: "Are you sure?",
        text: "This action cannot be undone!",
        cancelButtonText: 'Cancel',
        type: "error",
        showCancelButton: true,
        confirmButtonColor: "#DD6B55",
        confirmButtonText: "Yes, delete this ride.",
        closeOnConfirm: false
      }, () => {
        RideService.destroy(this.ride)
          .then(() => {
            swal({
              title: "Ride successfully deleted",
              type: "success",
              showCancelButton: false,
              timer: 2000,
              showConfirmButton: false
            });
            this.$router.go('/administration/rides');
          });
      });
    }
  }

To prevent sending multiple requests when the user clicks rapidly, the button should be disabled after the first click.

--EDIT--

import swal from 'sweetalert';
import RideService from '../../services/RideService';

export default {

  data () {
    return {
      ride: { user: {}, location: {}, type: {} },
      deleting: false
    }
  },

  route: {
  data ({ to }) {
      return RideService.show(this.$route.params.rideId)
        .then(function(data) 
        {
          this.ride = data.data.ride;
        }.bind(this));
    }
  }, 

  methods: {
    remove ()
    {
      if (!this.deleting) {
        this.deleting = true
        swal({
          title: "Are you sure?",
          text: "This action cannot be undone!",
          cancelButtonText: 'Cancel',
          type: "error",
          showCancelButton: true,
          confirmButtonColor: "#DD6B55",
          confirmButtonText: "Yes, delete this ride.",
          closeOnConfirm: false
        }, () => {
          RideService.destroy(this.ride)
            .then(() => {
              swal({
                title: "Ride successfully deleted",
                type: "success",
                showCancelButton: false,
                timer: 2000,
                showConfirmButton: false
              });
              this.deleting = false
              this.$router.go('/administration/rides');
            });
        });

        this.deleting = false
      }  
    }
  }
}       
</script>

--EDIT 2--

<template>
  <div class="row center">
    <div class="panel ride">
      <div class="ride-title bar-underline">
        <div class="ride-item-title">
          <strong class="ride-item-title-body">Ride on {{ ride.created_at }}</strong>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Name</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.user.name }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>From Location</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.from }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>To Location</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.from }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Description</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.description }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Kmz</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.kmz }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>kmp</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.kmp }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Hours</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.hour }} hours</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Google maps</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.location.maps }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Date</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.created_at }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Time</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.time }}</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Billable Time</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.billabletime }} hours</p>
        </div>
      </div>

      <div class="ride-item bar-underline">
        <div class="ride-item-title">
          <p>Type</p>
        </div>

        <div class="ride-item-content">
          <p>{{ ride.type.name }}</p>
        </div>
      </div>

      <div class="ride-item">
        <div class="ride-edit">
          <p>Edit</p>
        </div>

        <div class="ride-delete" @click="remove">
          <p>Delete</p>
        </div>
      </div>
    </div>
  </div>            
</template>

<script>

import swal from 'sweetalert';
import RideService from '../../services/RideService';

export default {

  data () {
    return {
      ride: { user: {}, location: {}, type: {} },
      processing: false
    }
  },

  route: {
  data ({ to }) {
      return RideService.show(this.$route.params.rideId)
        .then(function(data) 
        {
          this.ride = data.data.ride;
        }.bind(this));
    }
  }, 

  methods: {
    remove ()
    {
       if (this.processing === true) {
        return;
      } 
        this.processing = true
        swal({
          title: "Are you sure?",
          text: "This action cannot be undone!",
          cancelButtonText: 'Cancel',
          type: "error",
          showCancelButton: true,
          confirmButtonColor: "#DD6B55",
          confirmButtonText: "Yes, delete this ride.",
          closeOnConfirm: false
        }, () => {
          RideService.destroy(this.ride)
            .then(() => {
              swal({
                title: "Ride successfully deleted",
                type: "success",
                showCancelButton: false,
                timer: 2000,
                showConfirmButton: false
              });
              this.processing = false
              this.$router.go('/administration/rides');
            });
        });

        this.processing = false
    }
  }
}       
</script>

Answer №1

Starting from Vue version 2.1.4, a straightforward solution has been introduced for this issue:

Replace:

<div class="ride-delete" @click="delete">
   <p>Delete</p>
</div>

With:

<div class="ride-delete" @click.once="delete">
   <p>Delete</p>
</div>

The use of @click.once ensures that the specified method is executed only once.

In a scenario like mine, this resolved an issue during login, where repeated clicks caused multiple path chunks to be appended to the URL, leading to:

localhost:8000/admin/oms/admin/oms/admin/oms
.

You can find more details in the official Vue documentation here: https://v2.vuejs.org/v2/guide/events.html#Event-Modifiers

Answer №2

One approach could be to keep track of the async request status in a data property (e.g. processing: false). When the user triggers the action, set it to true, then check this state within the delete() method to determine whether to proceed or not. Remember to reset the state in both success and failure scenarios.

Here's an example:

new Vue({
  el: '#app',
  
  data: {
    processing: false
  },
  
  methods: {
    delete(el) {
      // Exit function if async request is already running
      if (this.processing === true) {
        return;
      } 
      
      // Set async state to indicate ongoing request
      this.processing = true;
      
      var paragraphs = Array.from(this.$el.querySelectorAll('p'));
      
      // Simulate async request with a delay
      setTimeout(() => {
        if (paragraphs.length) {
          paragraphs.shift().remove();
        }
        
        // Reset state on success/failure
        this.processing = false;
      }, 3000);
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<div id="app">
  Processing: {{ processing }} <br>
  <button @click.prevent="delete()">
    Click here to delete a paragraph 
  </button>

  <p v-for="1 in 3">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Officiis officia adipisci, omnis cum odit modi perspiciatis aliquam voluptatum consectetur. Recusandae nobis quam quisquam magnam blanditiis et quos beatae quasi quia!
  </p>

Answer №3

It's usually not recommended to use @click.once as it can interfere with form validation and may require resubmitting the form. Instead, a better approach is to utilize a flag like loading or processing in this scenario.

Answer №4

Give this a try

<div class="ride-delete" v-show="!deleting" @click="delete">
    <p>Delete</p>
</div>

methods: {
  delete ()
  {
    if (!this.deleting) {
      this.deleting = true

      swal({
        title: "Are you sure?",
        text: "This action cannot be undone!",
        cancelButtonText: 'Stop',
        type: "error",
        showCancelButton: true,
        confirmButtonColor: "#DD6B55",
        confirmButtonText: "Yes, delete this ride.",
        closeOnConfirm: false
      }, () => {
        RideService.destroy(this.ride)
          .then(() => {
            swal({
              title: "Ride successfully deleted",
              type: "success",
              showCancelButton: false,
              timer: 2000,
              showConfirmButton: false
            });
            this.deleting = false;
            this.$router.go('/administration/rides');
          });
      });
    }
  }
}

Answer №5

It is recommended to utilize the @click.once modifier that comes with vue version 2.1.4 or higher.

Answer №6

Effortless way (using vue directive)

<button v-click="ajaxPromiseFn">Prevent multiple clicks with ease</button>

// Easily prevent multiple clicks using the npm package vue-stop-multiple-click
const stopMultipleClick = require('vue-stop-multiple-click')
Vue.directive('click', stopMultipleClick)

Check out the online demonstration here

Visit the project's GitHub page

Get the npm package here

Answer №7

Give this a shot!

<div class="remove-ride" v-on:click="remove()">
    <p>Remove</p>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

TinyMCE file multimedia upload feature allows users to easily add audio, video

I am looking to enhance the functionality of my TinyMCE Editor by enabling file uploads for audio/video and images. Although image uploading is functioning properly, I am encountering issues with other types of files. Despite setting up pickers throughout, ...

New from Firefox 89: The afterprint event!

Having an issue with this fragment of code: const afterPrint = () => { this.location.back(); window.removeEventListener('afterprint', afterPrint); }; window.addEventListener('afterprint', afterPrint); window.print(); I&apos ...

Manipulating viewport settings to simulate smaller screens on a desktop device

I am working with a parent container that contains Bootstrap div.row elements holding div.col-... child elements. I am using jQuery to adjust the width and height of the container dynamically to replicate mobile device sizes for users to preview different ...

What is causing the Access-Control-Allow-Origin error when using axios?

I have a simple axios code snippet: axios.get(GEO_IP) .then(res => res) .catch(err => err); In addition, I have configured some default settings for axios: axios.defaults.headers["content-type"] = "application/json"; axios.defaults.headers.common. ...

"Adding a class with jQuery on a selected tab and removing it when another tab is clicked

I have 3 different tabs named Monthly, Bi-monthly, and Weekly. The user can set one of these as their default payroll period, causing that tab to become active. I was able to achieve this functionality by utilizing the following code within the <script& ...

Steps to link two mat-autocomplete components based on the input change of one another

After reviewing the content on the official document at https://material.angular.io/components/autocomplete/examples I have implemented Autocomplete in my code based on the example provided. However, I need additional functionality beyond simple integrati ...

Oops! The Route.get() function in Node.js is throwing an error because it's expecting a callback function, but it received

Currently, I am learning how to create a web music admin panel where users can upload MP3 files. However, I have encountered the following errors: Error: Route.get() requires a callback function but received an [object Undefined] at Route. [as get] (C:&bso ...

What is the process of invoking a function on a specific element when it is encapsulated within an if statement in Meteor.js

Here is an example: {{#if currentUser}} <li><a class="waves-effect waves-light btn modal-trigger modal-close" href="#upload">Upload Image</a></li> {{/if}} Currently, I am implementing the following: Template.MasterLayout.onRe ...

The res.send() method in Restify was not triggered within the callback function of an event

Currently, I am utilizing restify 2.8.4, nodejs 0.10.36, and IBM MQ Light messaging for a RPC pattern. In this setup, when the receiver has the result ready, it emits an event. The below restify POST route is supposed to capture this event along with the ...

Incorporating CSS animations into Vue.js while an API call is being made

When a specific icon is clicked, an API call is triggered: <i class="fas fa-sync" @click.prevent="updateCart(item.id, item.amount)"></i> I am looking to add an animation to rotate the icon until the API call is complete or ...

Load styles in Nuxt asynchronously to improve performance

Is there a way to load styles asynchronously on the website or insert them in the footer? I am currently using Nuxt version 2.0.0 Here's what I have tried so far: Adding a plugin in webpack called async-stylesheet-webpack-plugin. However, this res ...

Unlocking the res property in index.js from an HTML script tag: A step-by-step guide

Can anyone help me with the access variable issue I am facing? I have two files, index.js and page.ejs. These files require me to create a timer linked with datetimes stored on my local server. //index.js.. router.get('/mieiNoleggi', functio ...

Find the index of the element that was clicked by utilizing pure JavaScript

I am struggling to find the index of the element that was clicked. Can anyone help me with this? for (i = 0; i < document.getElementById('my_div').children.length; i++) { document.getElementById('my_div').children[i].onclick = f ...

Displaying properties of objects in javascript

Just starting with JavaScript and struggling to solve this problem. I attempted using map and filter but couldn't quite implement the if condition correctly. How can I find the records in the given array that have a gender of 0 and color is red? let s ...

Bring in TypeScript property from an external scope into the current scope

I am encountering an issue with my TypeScript code. Inside the anonymous functions, I am unable to change the properties of the class because they are out of scope. Is there a way to pass them in so that they can be modified? class PositionCtrl { ...

Center column with header and footer flanking each side

Trying to replicate the layout of the Facebook Android app on my page. The app features a 3 column design, with the central column exclusively displaying the header (no footer included, although I require one for my version). This visual representation is ...

"Encountered an error while trying to define a Boolean variable due

I am in the process of creating a TF2 trading bot with price checking capabilities. I encounter an issue while trying to define a boolean variable to determine if the item is priced in keys or not. My attempt at replacing isKeys with data[baseName].prices ...

What is the best way to fix multiple dropdown menus opening simultaneously in Vue.js?

I am working on an application that generates todo lists for tasks. These lists can be edited and deleted. I am trying to implement a dropdown menu in each list to provide options for updating or deleting the list individually. However, when I click on the ...

Send a JavaScript variable to Twig

I am trying to pass a JavaScript variable to a twig path but the current method I am using is not working as expected. <p id="result"></p> <script> var text = ""; var i; for (varJS = 0; varJS < 5; varJS++) { text += "<a href= ...

Creating a multi-tiered cascading menu in a web form: Harnessing the power of

In my form, there is a field called 'Protein Change' that includes a multi-level dropdown. Currently, when a user selects an option from the dropdown (for example, CNV->Deletion), the selection should be shown in the field. However, this function ...