Callback function in beforeRouteEnter is not triggered - javascript

I have simple routes: /follower/:token/edit and /follower/new
When I move from the first one to the second one by $router.push('/follower/new') befoureRouteEnter hook is triggered but callback function inside 'next' function does not (problem does not exist when I go from different routes or reload page).
beforeRouteEnter(to, from, next) {
debugger; //is triggered
next(vm => {
debugger; //is not triggered
})
}
Do you know what can be wrong?
Vue: 2.5.17
Vue-router: 3.0.1
Regards

If you are navigating between routes using the same component - vue tries to optimize by delivering a cashed version. I'm not sure if this is what you are experiencing – but you could try to force re-instantiation by adding a key value to your <router-view>.
A 'common' way to do this is to use $route.path
<router-view :key="$route.path"></router-view>

Related

VueJS lifecycle order when combined with router

I need to run an async initialize method when my app starts. so I did it inside beforeCreate of App.vue as below.
app.vue:
export default {
...
beforeCreate () {
console.log('before create app.vue')
}
}
Also, I need to check a condition before entering any route, so I did it inside beforeEach of router.
main.js:
router.beforeEach((to, from, next) => {
if (condition) {
console.log('router before each')
}
})
The problem is I need to check the condition after my initialization is done, but, when I launch my app, the condition is checked before the initialization and my output is:
router before each
before create app.vue
What am I missing? and how can I fix this?
here is how I solved it in vue3 but you can use a similar approach... basically don't mount the app until the initialization is completed. In my case the authCheck in the initialization function you mention full source
import useFirebaseAuth from "./hooks/firebase-auth";
const { authCheck } = useFirebaseAuth();
const app = createApp(App).use(IonicVue);
authCheck()
.then(() => {
app.use(router);
return router.isReady();
})
.then(() => {
app.mount("#app");
});
Yap that is how it suppose to be, the navigation guard is always triggered whenever there is a navigation, meaning it gets executed once it receives your navigation, which is before your component gets created. You can execute anything within your main.js file i don't fully understand what you want to achieve but you can utilize any of the Vue Js lifecycle hooks(https://v2.vuejs.org/v2/guide/instance.html) to get what you want or the navigation guards(https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards) beforeRouteEnter, beforeRouteLeave and beforeRouteUpdate
They all execute at different times, hope this helps

Next() is not being called on beforeRouteUpdate using the same component

I am using the same vue-component to display data that I'm fetching with axios. After clicking on a link the ajax-request is fetching new data.
But if the component is already loaded and the user clicks another link to load different data, the next()-method is not called.
Check out my codepen to see what I mean. There should be an alert showing. If you click a link for the first time, it does. If you switch to another link, it does not.
https://codepen.io/spqrinc/pen/YzXGGGL
From the docs:
Note that beforeRouteEnter is the only guard that supports passing a callback to next. For beforeRouteUpdate and beforeRouteLeave, this is already available, so passing a callback is unnecessary and therefore not supported
So the next() in beforeRouteUpdate can't take a callback because you already have access to the instance in this. Anything you would have done with vm in that callback can be done without it using this:
beforeRouteUpdate(to, from, next) {
alert("Reloaded");
// `this` works
next();
}

Re-evaluate dynamic import on vue-route change

I am making an admin panel that is used to manage several websites. My vue-router routes are all prefixed with the website slug and I have a beforeEach on the router that sets the website_slug in the vuex state accordingly.
Each site will use most of the same components, but there will also be several sites that have custom page components.
For this case I have made a function called importOrDefault like this:
const importOrDefault = (componentName, store) => {
return import(/* webpackChunkName: "[request]" */ `./Pages/${store.getters.website.slug}/${componentName}`)
.catch(() => import(/* webpackChunkName: "[request]" */ `./Pages/_shared/${componentName}`));
};
Which I use in my router config like this:
{
path: '/:website_slug/',
name: 'dashboard',
component: () => importOrDefault('Dashboard', store)
}
This setup works great on load. However, when I navigate to another :website_slug, the component is not re-evaluated and the same component as the previous site is loaded. The state has changed though, so this is not the problem.
Is there a way that I can make the router component re-evaluate the function or am I missing some obvious functionality I can use for this?
Vue Router tries to reuse components as much as it can. Have you tried adding beforeRouteUpdate to your component to explicitly set the data (vs using mounted or something else)? You can also set the key property of the component to something explicit (like the website_slug value) to ensure the component isn't reused.
I personally prefer using key when I can.
See: https://github.com/vuejs/vue-router/issues/1490
beforeRouteUpdate method: https://jsfiddle.net/Linusborg/L7hscd8h/2208/
key method: https://jsfiddle.net/Linusborg/L7hscd8h/1528/
I have tried several solutions, but couldn't get the route component to re-evaluate. Eventually, I loaded the available websites before bootstrapping Vue and generated all possible routes for all websites.
This way, the same page for another website is a unique route and the component is evaluated for every website.

Vue-router callback when navigation is cancelled

My Vue app shows a loading screen whenever a route is loading because code-split routes sometimes take a few seconds to load. It does this using a global beforeEach hook.
// App component
this.$router.beforeEach(() => { this.loading = true })
this.$router.afterEach(() => { this.loading = false })
this.$router.onError(() => { this.loading = false })
The problem with this is if I create a guard on a route definition (like beforeEnter), calling next(false) won't trigger afterEach or onError, so it leaves the app in a loading state forever.
One thing I tried was to use beforeResolve instead of beforeEach. This didn't work out because my beforeEnter route guards are asynchronous, and I want to show the loading screen while they execute.
Another possible solution is to always call next(error) instead of next(false) so that the onError hook is triggered. But that's a bug waiting to happen later on, when someone tries to use the navigation guard API as intended.
One last option is to isolate the this.loading logic into a globally-accessible class or event bus and make route guards handle it on a case-by-case basis.
But my preference would be to set it and forget it. Is there a global hook that gets called when navigation is cancelled? And why isn't afterEach called in this scenario?

vue-router's beforeEach guard exhibiting weird behaviour occasionally

I've got the following code:
const routes = [
{ path: '/', component: FooView },
{ path: '/bar', component: BarView }
];
const router = new VueRouter({
routes
});
router.beforeEach(function(to, from, next) {
if (to.path === '/bar') {
next('/');
}
next();
});
If I've omitted too much and you need to see other pieces of code related to the router let me know so I can fill it in.
If I open up a new tab and navigate to '/#/bar' I'm successfully redirected to '/#'. However, if I then go into the address bar and manually add '/#/bar' and hit enter I am not redirected. If I then hit enter in the address bar again I am redirected.
I've stepped through the code in the console and I see that it is calling next('/') and I see where it calls push('/') inside of next('/') but it doesn't take affect until I've hit enter in the address bar a second time.
I've tried using router.replace('/') but the behaviour is the same. I've tried using beforeEnter on the individual route but the behaviour is also the same.
Two links I've found where similar behaviour is discussed are: https://github.com/vuejs/vue-router/issues/748 and https://forum.vuejs.org/t/router-beforeeach-if-manually-input-adress-in-browser-it-does-not-work/12461/2 but neither helped me.
Is someone able to explain this? Is there a disconnect between what I'm trying to do and what functionality vue-router provides? If this behaviour isn't expected can someone propose a work around?
In the vue-router official documentation, the way you implement the beforeEach() is not recommended. Here is what the documentation says:
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors. Here is an example of redirecting to user to /login if they are not authenticated:
// BAD
router.beforeEach((to, from, next) => {
if (!isAuthenticated) next('/login')
// if the user is not authenticated, `next` is called twice
next()
})
// GOOD
router.beforeEach((to, from, next) => {
if (!isAuthenticated) next('/login')
else next()
})
Not sure why the first one is a bad example, since both sample code should work exactly the same logically. My code pops error when the first time redirect the path using next('/'), however, the rerouting still success. Looking for answer from pros.
Without getting too excited (a lot of testing left to do) it appears as though I've managed to fix my issue.
Instead of:
router.beforeEach(function(to, from, next) {
if (to.path === '/bar') {
next('/');
}
next();
});
I changed the code to the following:
router.beforeEach(function(to, from, next) {
if (to.path === '/bar') {
next('/');
return;
}
next();
});
Note the added return; in the if statement.
I still have questions about the behaviour. In particular I need too investigate deeper why sometimes it would hit the route when the only difference is whether it is the first or second time I entered the URL into the address bar. I'm sure diving deeper into next will answer my question.
Anyway, adding the return; turned this into a non blocker.
As other answers mention, the next() function should be called exactly once inside the same guard.
It is important that it is called at least once or the guard will never complete and block the navigation.
As taken from the docs -->
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors
The next() function signals that this guard has finished and the next guard may be called.
By doing this it is possible to create asynchronous guards that only finish when the next() function is called.
However the rest of the code inside the guard is still executed.
This means that if you call next() several times it will cause unpredictable behavior.
// This will work
if(condition){
next();
}
else{
next();
}
// This cause you pain!
next();
next();
The 'next' function also takes parameters:
// This aborts the current navigation and "stays" on the same route
next(false)
// These navigate to a different route than specified in the 'to' parameter
next('/');
next({path: '/'})
next({path: '/', query: {urlParam: 'value'}}) // or any option used in router.push -> https://router.vuejs.org/api/#router-forward
// If the argument passed to next is an instance of Error,
// the navigation will be aborted and the error
// will be passed to callbacks registered via router.onError().
next(new Error('message'))

Categories

Resources