Preventing Actions in Vue Router When a User Clicks on Menu Links

When dealing with JavaScript Single Page applications, such as Vue.js, and you have a form with a lengthy submit action (like saving something), it is crucial to handle the scenario where the user navigates away before the operation completes. In this case, the submit action should save the data and then redirect to a new route for a success message.

If the user navigates to a different link while waiting for the result, it can cause issues.

Take a look at this fiddle:

https://jsfiddle.net/hajbgt28/4/

const Home = { 
  template: '<div><button @click="submit">Save and go Bar!</button></div>',
  methods: {
     async submit() {
            await setTimeout(() => {
           this.$router.push("/bar");
        }, 5000);
     }
  }
};
const Foo = { template: '<div>Foo</div>' }
const Bar = { template: '<div>Bar</div>' }

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '/', component: Home },
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]
})

new Vue({
    router,
  el: '#app',
  data: {
    msg: 'Hello World'
  }
})
  1. Click Home
  2. Click the button
  3. Click on "Foo" immediately, you see "Foo"
  4. Wait a few seconds
  5. The Page changes to "Bar"

Two solutions come to mind:

  • Incorporate logic in the submit operation to check if the user is still on the intended route before proceeding. However, this approach can become complex.
  • Disable all links on the page during loading to prevent users from navigating away. But this can hinder the usability of the page until the operation finishes.

What would be considered best practice for handling situations like this?

Answer №1

To prevent an action from occurring when switching routes, you can utilize a beforeRouteLeave navigation guard.

  1. If your submit actions are identifiable, keep track of the ID of the operation result (e.g., store the timer ID returned by setTimeout in your example).
  2. Include a beforeRouteLeave handler in the component to halt the submit action (i.e., clear the timer ID in your example).
const Home = {
  methods: {
    submit() {
      this.timerId /* 1 */ = setTimeout(() => {
        this.$router.push("/bar");
      }, 5000);
    }
  },
  beforeRouteLeave (to, from, next) {
    clearTimeout(this.timerId) /* 2 */
    next()
  }
};

Check out the updated jsfiddle here

Answer №2

Consider this approach: create a component that offers functionality through Vue's provide/inject API:

  1. An operation initiator function, which is triggered when a form is submitted. It provides a whenDone callback that is executed or ignored based on the cancellation status of the operation.
  2. A function to cancel all pending operations. This could be useful when the user navigates away from the page.

The component implementation can be structured like this:

const CancellableOperationProvider = {
  name: "CancellableOperationProvider",
  props: {},
  data: () => ({
    pendingOperations: []
  }),
  
  provide() {
    return {
      $addOperation(func) {
        this.pendingOperations.push(func);
        func(function whenDone(callback) {
          if (this.pendingOperations.includes(func)) callback();
        });
      },
      $cancelAllOperations() {
        this.pendingOperations = [];
      }
    };
  },

  render() {
    return this.$slots.default[0];
  }
};

To utilize this component, you can apply it as shown below:

const Home = { 
  template: '<div><button @click="submit">Save and go Bar!</button></div>',
  inject: ['$addOperation', '$cancelAllOperations'],

  methods: {
    async submit() {
      this.$addOperation(whenDone => {
        await setTimeout(() => {
          whenDone(() => this.$router.push("/bar"));
        }, 5000);
      });
    }
  }
};

Additionally, you can include a navigation guard in Vue Router to automatically invoke $cancelAllOperations when any link is clicked. As $cancelAllOperations is accessed via the inject API, you may need to create a component that dynamically attaches and detaches the navigation guard to the Vue router upon mounting and unmounting.
Feel free to reach out if you encounter any issues—I'm a bit rusty with Vue these days.

Answer №3

After seeking advice from tony19, I have implemented a custom solution that caters to my specific use cases without relying on setTimeout:

const CustomComponent = { 
  template: '<div><button @click="submit">Save and proceed to Bar!</button></div>',
  data() {
    return {
        onPage: true
    }
  },
  beforeRouteLeave(to, from, next) {
    this.onPage = false;
    next();
  },
  methods: {
     submit() {
           if (this.onPage) {
                this.$router.push("/bar");
           }
     }
  }
};

To view the implementation, visit: https://jsfiddle.net/ovmse1jg/

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

Executing a Visual Basic subroutine from a jQuery dialog box

Currently, I have a form that includes a jQuery dialog creation. Within this dialog, there is a button generated from an ASP server control that should trigger a function in the code behind when clicked. However, I am encountering an issue where the functi ...

The global variable remains unchanged after the Ajax request is made

I am attempting to utilize AJAX in JavaScript to retrieve two values, use them for calculations globally, and then display the final result. Below are my code snippets. // My calculation functions will be implemented here var value1 = 0; var v ...

Using require to access an Immediately Invoked Function Expression variable from another file in Node.js

File 1 - Monitor.js var MONITOR = (function () { // Code for Monitoring return { doThing: function() { doThing(); } }; })(); File 2 - Test.js var monitor = require('../public/js/monitor.js'); I am trying to access the doThing() funct ...

Ways to populate dynamic choices for multiple Select boxes within an ng-repeat loop

When I click the "Add Row" button on an Html form, dynamic rows are added. Each row contains a 'Country' select and a 'State' select. The issue I am facing is that when I select a country in one row, all other row values change as well. ...

What is the best way to receive a notification when a user chooses any option in a Material UI Autocomplete React component?

My dilemma involves using an autocomplete feature that doesn't behave as expected. I am looking to detect when an option is selected, regardless of whether it's the same as the last selection. The onChange event I'm using only triggers if th ...

Is there a way to use Protractor to test ASP.NET WebForms (not Angular)?

I am completely new to using protractor for testing .NET Applications. I am currently in the process of creating an automation testing script from scratch. The following is the HTML code: <div class = "top"> <span id = "welcome"> <em>Hi& ...

Unable to perform a fetch request in IE9 after the page has finished loading

I'm currently encountering an issue with my Node and Express server setup. I have a separate API on a different server that needs to be accessed, but everything works fine except in IE9. The problem arises when I try to make a call to the API after lo ...

I'm looking for help on creating a three-column table using javascript/jquery. The table should display Product, Price, and Discount (which is calculated at 20%). Can anyone

Check out this code snippet. var items = ["Laptop", "Tablet", "Smartphone", "Headphones", "Camera"]; var costs = [599.99, 299.99, 799.99, 149.99, 499.99]; displayItems = ""; totalCost = 0; for (var j = 0; j < items.length; j++) { displayItems += " ...

Building Firestore subcollections with the latest WebSDK 9: A step-by-step guide

I'm looking to create a subcollection within my document using the webSDK 9, specifically the tree-shakable SDK. While I have come across some solutions, they all seem to be outdated and reliant on the old sdk. Just for context, I am working with Ne ...

Utilize $emit without the need for a click event

Vue is new to me and there are still some aspects that I haven't completely grasped. https://i.sstatic.net/gkTeI.png I noticed that in the first child component, there is a click event used to $emit information to the next child component. However, ...

Error message occurs when creating a pie chart with invalid values for the <path> element in Plottable/D3.js

For those who need the code snippets, you can find them for download here: index.html <!doctype html> <html> <head> <meta charset="UTF-8"> <!-- CSS placement for legend and fold change --> </head> <body ...

Is there a way to continuously fade and animate elements?

Upon running this code, I encountered an issue where $(".box1") does not fade in and animate when $(".box3").click is triggered; instead, it is directly displayed on the window. Additionally, there seem to be some problems with $(".box2") and $(".box3") af ...

Guide on transitioning from a WebGL renderer to a canvas renderer in three.js

My goal is to display a scene using either a WebGL renderer or a canvas renderer in three.js (version 69). This is the code I am using: <!DOCTYPE html> <html> <head> <script src="./libs/three.js"></script> <scri ...

Refresh the Document Object Model (DOM) and transmit the present time

I am having an issue with sending the actual current time when a button is clicked. Instead of getting the current time, I am receiving the time when the page initially loaded. This button is used to submit a form on Google Sheets using an API. This is th ...

Need help with checking for the presence of a value in a multidimensional array using Node.js/Javascript?

Imagine you have an array structured like this: $game = Array ( ['round'] => Array ( ['match'] => Array ( ['player_2'] => Array ( ...

Attempting to send a Promise to another function for it to return, encountering an error of "Unhandled promise rejection"

My goal is to develop a versatile database update function that can be utilized for creating more customized update functions. Within the module database.js, the following code is present: const {Pool,Client}=require('pg'); const pool=new Pool( ...

Having issues with the $addToSet method in my MongoDB API implementation

Despite searching through various topics, I couldn't find a solution to my specific problem. In an effort to enhance my JavaScript skills, I embarked on creating a quote generator. I successfully developed the API and frontend components. However, c ...

Activate continuous speech identification

Is it possible to activate the capability of recognizing continuous speech through the REST API (using javascript SDK) with the Bing Speech API? The Javascript SDK example available at https://github.com/Microsoft/Cognitive-Speech-STT-JavaScript only seem ...

Tips for centralizing error handling in Vue.js components

Within my component, I frequently use axios with then().catch() where I always include console.error() in the catch block like this: axios.get( //... ).then( //... ).catch( error => { console.error(..) } ) In addition to these instances, there a ...

Nuxt Integration for Amazon Native Shopping Ad

I am looking to integrate Amazon Native Shopping Ad onto my website. Here is an example of the Ad code structure: <script type="text/javascript"> amzn_assoc_tracking_id = "xxxxxxx"; amzn_assoc_ad_mode = "manual"; amzn_as ...