Vue-router: Route push to same named route with different params will not execute beforeRoute guards

Created on 12 Dec 2016  ·  11Comments  ·  Source: vuejs/vue-router

NOTE: I cannot create a fiddler right now. I will update the ticket later, when I can, with a fiddler. However, I did link to the source that was the problem. I verified this in my browsers debug tools (chrome).

Vue.js / vue-router versions

2.0.7 / 2.0.2

Steps to reproduce

  1. Define a route that has a beforeEnter guard (See below for my use case)
  2. Call router.push() from that route to the same route but with different parameters (i.e. /requests/1 to /requests/2)

What is Expected?

The route transition is executed AND the beforeEnter guard is executed.

What is actually happening?

Essentially I have a route that is defined as such:

{
    path: '/request/:id',
    name: 'showRequest',
    component: ShowRequest,
    beforeEnter: (to, from, next) => {
        store.dispatch('fetchRequest', to.params.id)
        next()
    }
}

Inside my Vue I have a method that performs the following:

api.clone(this.request, function(response) {
     router.push({ name: 'showRequest', params: { id: response.id } })
})

The API call is returning a cloned object but with a unique ID. I wish to transition to the showRequest route but with the different parameter. What happens is that the route (url in my browser) is set, but the beforeEnter guard is not executed. Therefore, the URL is updated but the fetchRequest Vuex action is never called.

I ended up attaching a debugger to understand what is going on. The first thing I verified was that the isSameRoute was returning false (see isSameRoute). Since the paths are not equal (different ID at the end), this function returns false.

The next thing to happen is that the router is attempting to resolveQueue. This is where things break down. The two parameters passed in, this.current.matched and route.matched are equal. Therefore the activated and deactivated arrays are empty. Therefore the beforeEnter guard I have is never executed.

The problem is the comparison being done between the two routes . Slicing an array at its final index will undoubtedly return an empty array. This is precisely what is happening. Some sort of different comparison or override is needed.

2.x

Most helpful comment

@fnlctrl @posva Thanks for the update. For now, I will use the $watch on the route to detect changes in the params.

All 11 comments

If I remove the resolveQueue call, and instead do:

const deactivated = this.route.matched
const activated = route.matched

My callbacks are executed. Why is resolveQueue necessary?

Alright, I took the time to create a fiddle that models what I am doing. Perhaps I am just doing things wrong, but essentially if you click on 'Bar' you will notice that the console logs here. If you then click on Not-Bar there will be no console log.

@predhme, workaround.

router.beforeEach((to, from, next) => {
    let isDelegated = false;

    for (let matched = (to.matched || []), i = matched.length; i--;) {
        let route = matched[i];

        if (route.beforeEnter) {
            isDelegated = true;
            route.beforeEnter(to, from, next);
        }
    }

    !isDelegated && next();
});

or

let router = new Router({
    routes: [
        path: '/request/:id',
        name: 'showRequest',
        component: ShowRequest,
        meta: {
            beforeEach: (to, from, next) => {
                store.dispatch('fetchRequest', to.params.id);
                next();
            }
        }
    ]
});
router.beforeEach((to, from, next) => {
    let isDelegated = false;

    for (let matched = (to.matched || []), i = matched.length; i--;) {
        let route = matched[i];

        if (route.meta.beforeEach) {
            isDelegated = true;
            route.meta.beforeEach(to, from, next);
        }
    }

    !isDelegated && next();
});

I closed my original pull request as it had a few errors. The second merge request is a squashed version of that.

@antdigo I appreciate the workaround. I will give that a shot.

@fnlctrl Hasn't we talked about this already. I couldn't find the issue but basically, the guard is not executed because it's the same route

@posva Yeah.. and I couldn't find the issue too, though I managed to find Evan's reply in https://github.com/vuejs/vue-router/issues/646

It's the same route with different params. You can react to param changes by watching $route.

So basically, @predhme in your case you should not be using before hooks to react to param changes, instead, you can simply add a watcher on $route, as described in http://router.vuejs.org/en/advanced/data-fetching.html

But there is also a proposal/feature request for adding a hook that is triggered on param changes:
https://github.com/vuejs/vue-router/issues/938

@fnlctrl @posva Thanks for the update. For now, I will use the $watch on the route to detect changes in the params.

Closing as this will be addressed when #938 is implemented.

With this "hack" using router.beforeEach when navigate back await router.back() it's completed before await route.meta.beforeEnter(to, from, next)

Was this page helpful?
0 / 5 - 0 ratings