Why does my Vue Router throw a Maximum call stack error? - vue.js

I have a really simple routing practically looks like this I'm using this under electron
import Vue from "vue";
import VueRouter from "vue-router";
import Projects from "../views/Projects.vue";
import RegisterUser from "#/views/RegisterUser.vue";
//import { appHasOwner } from "#/services/";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "projects",
component: Projects,
meta: {
requiresUser: true
}
},
{
path: "/register",
name: "register",
component: RegisterUser
},
{
path: "/settings",
name: "settings",
meta: {
requiresUser: true
},
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/Settings.vue")
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
router.beforeEach((to, from, next) => {
if (to.matched.some(route => route.meta.requiresUser === true)) {
//this will be for test case undefined
let user;
if (typeof user === "undefined") {
// eslint-disable-next-line no-console
console.log(user); //logs undefined but at the end no redirect
next("/register");
} else {
next();
}
}
});
export default router;
taking the example from the docs
// GOOD
router.beforeEach((to, from, next) => {
if (!isAuthenticated) next('/login')
else next()
})
the application can boot only if there is a user attached in database either should redirect to the register component but the code above will end with Maximum call stack size exceeded. So how to check with beforeEach conditions end redirect to a given page?

The Maximum call stack size exceeded is usually due to infinite recursion, and that certainly seems to be the case here. In router.beforeEach you're calling next to go to the /register route, which goes back into this method, which calls next, and so on. I see you have a requiresUser in your meta, so you need to check that in beforeEach, like this:
router.beforeEach((to, from, next) => {
// If the route's meta.requiresUser is true, make sure we have a user, otherwise redirect to /register
if (to.matched.some(route => route.meta.requiresUser === true)) {
if (typeof user == "undefined") {
next({ path: '/register' })
} else {
next()
}
}
// Route doesn't require a user, so go ahead
next()
}

Related

vue router 4 next() callback warning

I have unsucessfully been trying to fix this vue-router warning :
The "next" callback was called more than once in one navigation guard when going from "/" to "/". It should be called exactly one time in each navigation guard. This will fail in production.
I have read the official doc (https://next.router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards) but I keep getting infinite next loop when making the suggested changes, so I am obviously doing something wrong.
What I would like to do is :
Have non authenticated users only be able to navigate to "/" route. and get redirected to "/" if they try to enter a protected route.
Have authenticated users directly land on "/mapping" and skip "/" route. They can then nav to any protected routes.
I verify auth with "let user = projectAuth.currentUser".
Thanks in advance for your tips
import { createRouter, createWebHistory } from 'vue-router'
import Mapping from '../views/Mapping.vue'
import Home from '../views/Home.vue'
import TestDthree from '../views/TestDthree.vue'
import Testpage1 from '../views/Testpage1.vue'
import { projectAuth } from '../firebase/config'
// auth guard or route guard
// requires to be authenticated
const requireAuth = (to, from, next) => {
let user = projectAuth.currentUser
if(!user){
next({name:'Home'})
}
next()
}
// no auth needed
// bypassed Home for any logged in user
const requireNoAuth = (to, from, next) => {
let user = projectAuth.currentUser
if(user){
next({name:'Mapping'})
}
next()
}
const routes = [
{
path: '/',
name: 'Home',
component: Home,
beforeEnter: requireNoAuth
},
{
path: '/mapping',
name: 'Mapping',
component: Mapping,
beforeEnter: requireAuth
},
{
path: '/testpage1',
name: 'Testpage1',
component: Testpage1,
beforeEnter: requireAuth
},
{
path: '/loadLegacyData2',
name: 'LoadLegacyData2',
component: () => import( '../views/LoadLegacyData2.vue'),
beforeEnter: requireAuth
},
{
path: '/testmap',
name: 'TestDthree',
component: TestDthree,
beforeEnter: requireAuth
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
Just add returns in requireAuth:
const requireAuth = (to, from, next) => {
let user = projectAuth.currentUser
if(!user){
next({name:'Home'})
return
}
next()
return
}
Or
const requireAuth = (to, from, next) => {
next(projectAuth.currentUser ? to : {name:'Home'})
return
}
you have to check if you are not already on the page, otherwise it will loop indefinitely. Something like this
const requireAuth = (to, from, next) => {
let user = projectAuth.currentUser
if(!user && from.name !== 'Home'){
next({name:'Home'})
}
next()
}

Dynamic root url structure in Vue with vue-router, route guard & Vuex

I have a vue-router that looks like this:
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
children: [
{
{
path: 'main',
name: 'main',
component: () => import(/* webpackChunkName: "main" */ './views/main/Main.vue'),
children: [
{
path: 'dashboard',
name: 'main-dashboard',
component: () => import(/* webpackChunkName: "main-dashboard" */ './views/main/Dashboard.vue'),
},
...
There are route guards in place so that once a user is logged in they are directed to /BASE_URL/main/dashboard.
public beforeRouteEnter(to, from, next) {
routeGuardMain(to, from, next);
}
public beforeRouteUpdate(to, from, next) {
routeGuardMain(to, from, next);
}
const routeGuardMain = async (to, from, next) => {
if (to.name === 'main') {
next({ name: 'main-dashboard'});
} else {
next();
}
};
I'm storing user_id and account_id in a Vuex state and I'd like to be able to create a url structure like:
BASE_URL/<account_id>/dashboard
But I'm having trouble accessing the account_id from the store (I have getters setup to get the relevant params) and passing it as a parameter during the redirect in the route guard (its null / undefined, so I think I need to await somewhere??).
I can set up dynamic urls for paths which don't have a route guard, but not sure how to do it with them in place.
I've read through the vue-router docs, but can't work it out.
Please can anyone suggest how I can achieve the target url structure? Apologies my frontend skills are lacking and I'm new to Vue.js
Thank you!
Found a solution similar to this link:
Accessing Vuex state when defining Vue-Router routes
const startRouteGuard = async (to, from, next) => {
await dispatchCheckLoggedIn(store);
if (readIsLoggedIn(store)) {
if (to.path === '/login' || to.path === '/') {
if (store.getters.userMembership.account_id === null) {
const watcher = store.watch(store.getters.userMembership.account_id, account_id => {
watcher(); // stop watching
next({ name: 'main', params: { account_id: account_id}});
});
} else {
const account_id = store.getters.userMembership.account_id;
next({ name: 'main', params: { account_id: account_id}});
}
} else {
next();
}
} else if (readIsLoggedIn(store) === false) {
if (to.path === '/' || (to.name as string).startsWith('main')) {
next({name: 'login'});
} else {
next();
}
}
};

Vue-Router beforeEnter not function as expected?

I am trying to protect a route using beforeEnter. my route looks like such;
path: '/account',
name: 'account',
component: Account,
beforeEnter:
(to, from, next) => {
const authService = getInstance();
const fn = () => {
// If the user is authenticated, continue
if (authService.isAuthenticated) {
console.log('no')
return next();
}
// Otherwise, log in
console.log('should login')
authService.loginWithRedirect({ appState: { targetUrl: to.fullPath } });
};
if (!authService.loading) {
return fn();
}
authService.$watch("loading", loading => {
if (loading === false) {
return fn();
}
})
}
},
THIS functions as I expect, but I don't believe the logic should into the routes file, so simply enough I store it in a different file under my auth folder. Like so;
import { getInstance } from "./index";
export const authGuard = (to, from, next) => {
console.log('test')
const authService = getInstance();
const fn = () => {
// If the user is authenticated, continue with the route
if (authService.isAuthenticated) {
console.log('no')
return next();
}
// Otherwise, log in
console.log('should login')
authService.loginWithRedirect({ appState: { targetUrl: to.fullPath } });
};
// If loading has already finished, check our auth state using `fn()`
if (!authService.loading) {
return fn();
}
// Watch for the loading property to change before we check isAuthenticated
authService.$watch("loading", loading => {
if (loading === false) {
return fn();
}
});
};
However when I import this to my routes and do;
import { authGaurd } from './auth/authGaurd'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/account',
name: 'account',
component: Account,
beforeEnter: authGaurd
},
this no long works? I'm sure I must be missing something simple? Any help would be much appreciated.
Try:
- beforeEnter: authGaurd
+ beforeEnter(to, from, next) {
authGaurd(to, from, next)
}

How to setup vuex and vue-router to redirect when a store value is not set?

I'm working with the latest versions of vue-router, vuex and feathers-vuex and I have a problem with my router.
What I'm doing is to check if a route has the property "requiresAuth": true in the meta.json file. If it does then check the value of store.state.auth.user provided by feathers-vuex, if this value is not set then redirect to login.
This works fine except when I'm logged in and if I reload my protected page called /private then it gets redirected to login so it seems that the value of store.state.auth.user is not ready inside router.beforeEach.
So how can I set up my router in order to get the value of the store at the right moment?
My files are as follow:
index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
const meta = require('./meta.json')
// Route helper function for lazy loading
function route (path, view) {
return {
path: path,
meta: meta[path],
component: () => import(`../components/${view}`)
}
}
Vue.use(Router)
export function createRouter () {
const router = new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
route('/login', 'Login')
route('/private', 'Private'),
{ path: '*', redirect: '/' }
]
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
if (!store.state.auth.user) {
next('/login')
} else {
next()
}
} else {
next()
}
})
return router
}
meta.json
{
"/private": {
"requiresAuth": true
}
}
I fixed the issue by returning a promise from vuex action and then run the validations
router.beforeEach((to, from, next) => {
store.dispatch('auth/authenticate').then(response => {
next()
}).catch(error => {
if (!error.message.includes('Could not find stored JWT')) {
console.log('Authentication error', error)
}
(to.meta.requiresAuth) ? next('/inicio-sesion') : next()
})
})

Vue router navigation gaurd from within the component

I use vuex from centralized state management
in my vuex store.js i store the login status as a boolean value like below
export const store = new Vuex.Store({
state: {
loggedIn: false,
userName: 'Guest',
error: {
is: false,
errorMessage: ''
}
},
getters: {
g_loginStatus: state => {
return state.loggedIn;
},
g_userName: state => {
return state.userName;
},
g_error: state => {
return state.error;
}
}
)};
When the user logs in i set the loginstatus to true and remove the login button and replace it with log out button
everything works fine but the problem is when the user is logged in and if i directly enter the path to login component in the search bar i am able to navigate to login page again
I want to preent this behaviour
If the uses is logged in and searches for the path to loginpage in the searchbar he must be redirected to home page
I have tried using beforeRouteEnter in the login component
But we do not have acess to the this instance since the component is not yet loaded
So how can i check for login status from my store
my script in login.vue
script>
export default{
data(){
return{
email: '',
password: ''
};
},
methods: {
loginUser(){
this.$store.dispatch('a_logInUser', {e: this.email, p: this.password}).then(() =>{
this.$router.replace('/statuses');
});
}
},
beforeRouteEnter (to, from, next) {
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
//what shall i do here
}
})
}
}
It is much better to put the navigation guards in routes not in pages/components and call the state getters on route file.
// /router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
// Protected Pages
import Dashboard from '#/views/dashboard'
// Public Pages
import Dashboard from '#/views/login'
Vue.use(Router)
// If you are not logged-in you will be redirected to login page
const ifNotAuthenticated = (to, from, next) => {
if (!store.getters.loggedIn) {
next()
return
}
next('/') // home or dashboard
}
// If you are logged-in/authenticated you will be redirected to home/dashboard page
const ifAuthenticated = (to, from, next) => {
if (store.getters.loggedIn) {
next()
return
}
next('/login')
}
const router = new Router({
mode: 'history',
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/dashboard',
name: 'Home',
component: Full,
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: Dashboard,
beforeEnter: ifAuthenticated
},
]
},
{
path: '/login',
name: 'Login',
component: Login,
beforeEnter: ifNotAuthenticated
},
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
})
export default router
You can also use vue-router-sync package to get the value of store values
You can redirect the user to the home page or some other relevant page:
mounted () {
if(vm.$store.getters.g_loginStatus === true){
this.$router('/')
}
}
beforeRouteEnter (to, from, next) {
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
next('/')
}
})
}
From the docs:
next: Function: this function must be called to resolve the hook. The action depends on the arguments provided to next:
next(): move on to the next hook in the pipeline. If no hooks are left, the navigation is confirmed.
next(false): abort the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the from route.
next('/') or next({ path: '/' }): redirect to a different location. The current navigation will be aborted and a new one will be started.