Infinite loop with vue router beforeEach and children paths - vue.js

When i use beforeEach with children paths debug console show this error: vue-router.esm.js?8c4f:2079 RangeError: Maximum call stack size exceeded
import Vue from 'vue'
import VueRouter from 'vue-router'
import LoginMixin from '#/mixins/LoginMixin.js'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: require('#/views/Home.vue').default,
},
{
path: '/login',
name: 'login',
meta: { layout: 'centered' },
component: () => import('#/views/Login.vue'),
},
{
path: '/register',
name: 'register',
meta: { layout: 'centered' },
component: () => import('#/views/Register.vue'),
children: [
{
path: 'user',
component: () => import('#/components/RegisterForm.vue'),
},
{
path: 'company',
component: () => import('#/components/CompanyForm.vue'),
}
]
},
]
//creamos la instancia router modo history(urls amigables)
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
if (to.path != '/login' || to.path != '/register/user' && LoginMixin.methods.loginMixinCheckAuth() == false) {
//if not logead and join to other page distinc of login or register redirect to login
next('/login')
}
else {
next()
}
})
I dont know what is bad, the syntaxis is fine and function LoginMixin.methods.loginMixinCheckAuth() is working good (i tested without the function and result is the same).

Hmm at first glance I'd try to make this convoluted if in your beforeEach method simpler. Try to add something like requiresAuth: true to the meta of all your routes that require a logged in user.
In a sense you want something like this in your routes:
// ...
{
path: '/users/:userId(\\d+)/edit/',
name: 'EditUser'
props: true,
meta: {
requiresAuth: true, // <-- add this meta flag against which you check later in beforeEach
},
component: () => import(/* webpackChunkName: "user-edit" */ '#/views/UserEdit.vue'),
},
// ...
And this in your beforeEach:
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) { // <-- check for requiresAuth here
// assuming your login mixin works
// if I were you I'd store some JWT in localStorage and check that somehow in a vuex getter
if (!LoginMixin.methods.loginMixinCheckAuth()) {
next('/login')
} else {
next()
}
} else {
next()
}
})
To answer this in full would be kinda bulky so go and check out how I did that using meta here and implemented a beforeEach rule here

Related

Vue router i18n redirecting to duplicate locale in route

I Have a vue application with i18n and authentication via naviation guard.
My issue is that when im running:
#click="pushRouteTo(`${$i18n.locale}/list/` + f.id)"
pushRouteTo(route) {
try {
this.$router.push(route);
} catch (error) {
if (!(error instanceof NavigationDuplicated)) {
throw error;
}
}
}
I am getting pushed to example.com/en/en/list/123 instead of example.com/en/list/123
When i place a debugger in my navigation guard it says that my to.path is "/en/en/list/123"
but i am pushing /en/list/123. How can this be?
Does it have something to do with my redirect in my router?
Here is my router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home2 from './views/Home2.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import ErrorLanding from './views/ErrorLanding.vue'
import Root from "./Root"
import i18n, { loadLocaleMessagesAsync } from "#/i18n"
import {
setDocumentLang
} from "#/util/i18n/document"
Vue.use(Router);
const { locale } = i18n
export const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
redirect: locale,
meta: {authRequired: false},
},
{
path: "/:locale",
component: Root,
meta: {authRequired: false},
children: [
{
name: 'Home',
path: '',
component: Home2,
meta: {authRequired: false},
},
{
name: 'Login',
path: 'login',
component: Login,
},
{
path: 'register',
component: Register,
},
{
path: 'lockedpage',
name: 'lockedpage',
webpackChunkName: "lockedpage",
meta: {authRequired: true},
component: () => import('./views/LockedPage.vue')
},
{
path: '*',
component: ErrorLanding,
name: 'NotFound'
}
]
}
],
router.beforeEach((to, from, next) => {
const { locale } = to.params
const publicPages = [ `/`, `/${locale}`, `/${locale}/`];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
const redirect = to.path;
loadLocaleMessagesAsync(locale).then(() => {
setDocumentLang(locale)
}).then(() => {
if (authRequired && loggedIn === null) {
if(to.meta.authRequired === false) {
debugger;
next();
}
else {
debugger;
next({ name: 'Login', query: { redirect: redirect } });
}
} else {
debugger;
next();
}
})
});
The root to this issue was a missing slash in my route.
pushRouteTo(`${$i18n.locale}/list/` + f.id)
should instead be:
pushRouteTo(`${/$i18n.locale}/list/` + f.id)
thats why it was creating a double locale.

Vue router - navigation guard is skipped when using router.push()

I have a Vue SPA with i18n and some views that require authentication via navigation guard.
When i am not authenticated and go to url via my browser:
examplepage.com/en/lockedpage
i get redirected to:
examplepage.com/en/login
which is good, however when i click a button that runs:
#click="$router.push(`/${$i18n.locale}/lockedpage`)"
i get in to the page even if i am not authenticated.
I want to get redirected to the login page if not authenticated
this is my router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home2 from './views/Home2.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import ErrorLanding from './views/ErrorLanding.vue'
import Root from "./Root"
import i18n, { loadLocaleMessagesAsync } from "#/i18n"
import {
setDocumentLang
} from "#/util/i18n/document"
Vue.use(Router);
const { locale } = i18n
export const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
redirect: locale
},
{
path: "/:locale",
component: Root,
children: [
{
name: 'Home',
path: '',
component: Home2,
},
{
name: 'Home2',
path: '/',
component: Home2,
},
{
name: 'Login',
path: 'login',
component: Login,
},
{
path: 'register',
component: Register,
},
{
path: 'lockedpage',
name: 'lockedpage',
webpackChunkName: "lockedpage",
meta: {authRequired: true},
component: () => import('./views/LockedPage.vue')
},
{
path: '*',
component: ErrorLanding,
name: 'NotFound'
}
]
}
],
router.beforeEach((to, from, next) => {
if (to.params.locale === from.params.locale) {
next()
return
}
const { locale } = to.params
loadLocaleMessagesAsync(locale).then(() => {
setDocumentLang(locale)
const publicPages = [ `/`, `/${locale}`, `/${locale}/`];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
const redirect = to.path;
if (authRequired && loggedIn === null) {
if(to.meta.authRequired === false) {
next();
}
else
next({ name: 'Login', query: { redirect: redirect } });
} else {
next();
}
})
next()
});
Why does my navigationguard skip when using router.push() ?
This issue started after adding i18n, with localeredirect. so all routes comes after a locale for example: /en/.../
As Estus points out in the comments, the issue is that the first thing you're checking for is if the locales match, and if they do you're calling next() and sending the user to the page.
As outlined here:
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.
If you need to keep the locale check between the to and from pages, you could do something like:
if (to.params.locale === from.params.locale && loggedIn) {
next()
return
}
which will check if the loggedIn variable is truthy before pushing the user to the page they're trying to navigate to.
I believe just removing the if statement that checks if the locales match would also work.

How can I fix 'Unhandled Promise Rejection' for my Vue application?

For my Vue application I encounter the following issue in IE: 'Unhandled Promise Rejection undefined'.
I tracked the issue down to the authentication of the application. But I'm not sure how to tackle it. I've tried try-catch blocks around the next() functions.
Below the code of the authentication.
import router from '#/config/Router';
import CONST from '#/utils/Constants';
import ObjectHelper from "#/helpers/ObjectHelper";
class Auth {
constructor() {
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!Auth.isLoggedIn()) {
next({path: CONST.ROUTE.SESSION.LOGIN});
} else {
next();
}
} else {
next();
}
});
}
static logOut() {
localStorage.clear();
sessionStorage.clear();
router.replace(CONST.ROUTE.SESSION.LOGIN);
}
static isLoggedIn() {
return ObjectHelper.exists(localStorage.getItem(CONST.KEY.AUTH.ACCESS_TOKEN));
}
}
export default Auth;
After this.$router.push(CONST.ROUTE.ORGANISATIONS.OVERVIEW); in my login-component, the error is thrown.
To be complete, I also provide the code for the Router:
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
base: process.env.BASE_URL,
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import('#/views/home/home.vue')
},{
path: '/session',
name: 'session',
component: () => import('#/views/session/session.vue'),
children: [
{
path: 'login',
name: 'login',
component: () => import('#/views/session/login/login.vue')
},
{
path: 'logout',
name: 'logout',
component: () => import('#/views/session/logout/logout.vue')
}
]
},
{
path: '/organisations',
alias: '/organisaties',
component: () => import('#/views/organisations/organisations.vue'),
children: [
{
path: '',
name: 'organisations-overview',
component: () => import('#/views/organisations/overview/overview.vue'),
meta: {
requiresAuth: true,
can: 'edit-organisations',
fail: '/session/logout'
}
},
{
path: ':uuid',
name: 'organisation-edit',
component: () => import('#/views/organisations/edit/edit.vue'),
meta: {
requiresAuth: true,
can: 'edit-organisations',
fail: '/session/logout'
}
}
]
}
]
});
Anyone an idea how to tackle this? Many thanks!
I found a solution for 'Unhandled Promise Rejection undefined' in IE. I needed to add support for IE11 to my Vue project: 'babel-polyfill', 'weakmap-polyfill', 'core-js/es/set', 'core-js/es/map'. After this, everything works fine.

How to not be able to access manually /login after you logged in - VueJS?

I'm learning VueJS from an Udemy course. In the module about authentication, the instructor didn't make the whole process, so I had to try it by my self for 2 days but I succeeded 90%.
The backend is on firebase, so after login with correct data, I get back a token that I send it to local storage.
With the code that I make it, you can't see the dashboard if you are not authenticated(even you try the route manually), but what I don't like is that you can see the login page after you are authenticated(if you type /signin).
This last part is not normal to be. So if you are authenticated, when you try manually to go to /signin, you can.
In the router.js:
const routes = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: WelcomePage },
{ path: '/signup', component: SignupPage },
{ path: '/signin', component: SigninPage },
{ path: '/dashboard', component: DashboardPage}
]
});
routes.beforeEach((to, from, next) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/signin', '/signup'];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('token');
console.log(loggedIn);
if (authRequired && !loggedIn) {
return next('/signin');
}
next();
});
And in store.js, inside login action:
if (localStorage.getItem('token')) {
router.replace("/dashboard");
}
Any idea what to do to /login and /register routes after login so to not be able to see them, even you manually try these routes?
If the user will try manually /signin or /signup, I want to be redirected to /dashboard.
In my vue application I just use the router.beforeEach method with meta data plus the state of my token which I pull from my store.
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
var token = store.getters.loggedIn;
if (!token ) {
next({
path: '/login',
})
} else {
next()
}
}else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (token) {
next({
path: '/',
})
} else {
next()
}
}
})
It checks each time the route changes.
The requiresVisitor meta is something I placed in my router object
{
// this is can only be viewed if not logged in.
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'),
props: true,
meta: {
requiresVisitor: true,
layout: "landing",
},
},
{
// this can only be viewed if logged in.
path: '/',
name: 'dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
props: true,
meta: {
requiresAuth: true,
layout: "default",
},
},
you can read more about route guards Here
theses methods are typically used in the entry point to the app main.js or in your router.js file.
You can add a per-route guard (https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard) to make the logic only run when /signin and /signup is matched, but if you want to keep it in the loop that runs over all routes you're on the right track -- you just need to invert your logic.
So what you want do is to add another if statement, checking if isLoggedIn is true, and that you're trying to access a public page, and in that case redirect the user to the /dashboard route.
if (!authRequired && loggedIn) {
next('/dashboard');
return;
}
Best example of redirection using beforeEach
const routes = [
{
path: "/",
name: "login",
component: Login,
meta:{requiresVisitor: true},
},
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
meta:{requiresVisitor: false},
}];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
//Check the user is loged in If yes then move to next url.If not loged
in so move in else and check the meta and move into next other wise is
redirect my index url
router.beforeEach((to, from, next) => {
const isLogged = JSON.parse(localStorage.getItem('username'));
if (isLogged) next()
else{
if(to.meta.requiresVisitor) next()
else next('/')
}
})

Vue.js router - conditional component rendering

routes: [
{
path: '/',
name: 'home',
get component(){
if(Vue.loggedIn){
return Home
}else{
return Login
}
}
}
I've added a getter and seems to work fine but any variable or function i use in the if statement is undefined. even i've tried using global prototype.$var and mixin functions still with no success.
All i need is if a user is logged in the path '/' renders Dashboard view and if not then Login is rendered to '/'.
Note: I've used beforeEnter: and it works fine. but i don't want redirection.
Using your approach this is what I found is working:
routes: [
{
path: '/',
name: 'home',
component: function () {
if(loggedIn){
return import('../views/Home.vue')
}else{
return import('../views/Login.vue')
}
}
}
In my application I use a router.beforeEach to check if user has logged in. I used a getter to check if logged in state is correct. I also used meta to only show views depending on if user has logged in or not.
I applied this code to the entry point of the application main.js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.getters.loggedIn) {
next({
path: '/login',
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
// this route is only available to a visitor which means they should not be logged in
// if logged in, redirect to home page.
if (store.getters.loggedIn) {
next({
path: '/',
})
} else {
next()
}
}
})
In my router.js file I have the meta set as this
routes: [
{
// this is the route for logging in to the app
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ './views/Login.vue'),
props: true,
meta: {
requiresVisitor: true,
layout: 'landing',
},
},
{
// this is the dashboard view
path: '/',
name: 'dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
meta: {
requiresAuth: true,
layout: 'default',
breadCrumb: [
{ name: 'Dashboard' }
]
}
},
]
notice the meta object. if you are using vue devtools you will see that these params are available to use for validation.