Vue Router - catch all wildcard not working - vue-router

I'm using Vue Router with Vue 3 and am trying to add a catch-all route to redirect the user if they try and access an invalid URL. When I try and use the wildcard (*), i get the following error logged to the console:
Uncaught Error: A non-empty path must start with "/"
at tokenizePath (vue-router.esm.js?8c4f:975)
at createRouteRecordMatcher (vue-router.esm.js?8c4f:1106)
at addRoute (vue-router.esm.js?8c4f:1190)
at eval (vue-router.esm.js?8c4f:1335)
at Array.forEach (<anonymous>)
at createRouterMatcher (vue-router.esm.js?8c4f:1335)
at createRouter (vue-router.esm.js?8c4f:2064)
at eval (index.js?a18c:26)
at Module../src/router/index.js (app.js:1402)
at __webpack_require__ (app.js:854)
I'm assuming this is because I don't prepend the path containing the asterisk with a '/', but if I do this then the catch all doesn't work. Here are my routes:
imports...
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/user',
name: 'User',
// route level code-splitting
// this generates a separate chunk (user.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "user" */ '../views/user/User.vue'),
children: [{path: '', component: UserStart}, {path: ':id', component: UserDetail}, {path: ':id/edit', component: UserEdit, name: 'userEdit'}]
},
{path: '/redirect-me', redirect: '/user'},
{path: '*', redirect: '/'}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
The wildcard route is the last object in the routes array. Does anyone know what I'm doing wrong?

Catch all routes (/*) must now be defined using a parameter with a custom regex: /:catchAll(.*)
For example:
{
// path: "*",
path: "/:catchAll(.*)",
name: "NotFound",
component: PageNotFound,
meta: {
requiresAuth: false
}
}

Personally, for Vue 2's * (star or catch all) routes in Vue 3 I use:
{
path: '/:pathMatch(.*)*', <== THIS
name: 'not-found',
component: NotFound
}
Catch all routes (*, /*) must now be defined using a parameter with a custom regex:
The parameter name can be whatever you want like catchAll, pathMatch, noPage etc
{
path: '/:pathMatch(.*)*', //will match everything and put it under `$route.params.pathMatch`
name: 'not-found',
component: NotFound
}
{
path: '/user-:afterUser(.*)',// will match anything starting with `/user-` and put it under `$route.params.afterUser`
component: UserGeneric
}
/:pathMatch(.*)*
The last * it is necessary if you plan on directly navigating to the not-found route using its name.
If you omit it the / character in params, it will be encoded when resolving or pushing.
For example if you use path: /:pathMatch(.*) (note: without the last asterisk) and you go to /user/not-found (a page that doesn't exists) the this.$route.params.pathMatch will be a string => 'user/not-found'
// bad example if using named routes:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
Instead, if you use path: /:pathMatch(.*)* (note: with asterisk) this.$route.params.pathMatch will be an array ['user', 'not-found']
// good example:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
Please read docs: From migration from vue 2 to vue 3 and Catch all / 404 Not found Route

Related

Dynamically registered route is not working when it is just pushed

Hi i'm facing a problem where i want to add new route dynamically
My route structure will look something like this
I'm using "vue-router": "^3.5.3"
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, name:'User'
children: [
{
path: 'profile', component: Profile, name:'Profile'
children: [
{
path: 'about', component: About, name:'About'
children: [
{
path: 'details', component: Details, name: 'Details'
}
]
}
]
}
]
}
]
})
Now my intention is to add child route to Details
this is how my pseudo code looks like
findRouteWithName(routeObj,routeName)
{
// recursive find with any nested level
....
return foundRoute;
}
let routeObj = findRouteWithName(this.$router.options.routes,'Details');
routeObj.children.push(Route object{});
//this.$router.addRoutes([routeObj]) // this line creates problem as mentioned below in **problem:**
Note: if i'm doing like this https://stackoverflow.com/a/48833074/10054910 it is creating multiple nested routes in the url
Problem: page is becoming blank with direct push on children even route does not change in url. with this https://stackoverflow.com/a/48833074/10054910 approach route changes in url but append 2 times
Please help me thanks in advance !!

Vue router, Error: Redirected * to * via a navigation guard

Access /dashboard/profile get error
vue-router.esm.js:1958 Uncaught (in promise) Error: Redirected when going from “/home” to “/dashboard” via a navigation guard.
// router.ts
const routes: Array<RouteConfig> = [
{path: '/', redirect: {name: 'Home'}},
{path: '/home', name: 'Home', component: Home},
{
path: '/dashboard', component: () => import('../views/Dashboard.vue'),
meta: {isLogin: true},
children: [
{path: '', component: () => import('../views/Dash/Certificate/index.vue'), meta: {isViewer: true},
{path: 'profile', component: () => import('../views/Dash/ProfileComponent.vue')},
]
},
];
//guard
router.beforeEach((to, from, next) => {
const profilePath = "/dashboard/profile";
if (!to.matched.some(res => res.meta.isLogin)) {
return next();
}
// isNotLogin
if (!localStorage['currentUser'])
return next({path: "/", query: {redirect: to.fullPath}});
// isLogin
if (to.matched.some(res => res.meta?.isViewer)) {
viewService.isCertificateViewer ? next() : next({path: profilePath})
} else next()
});
I spent four hours trying to solve this same problem. This is the article I found that explained where the error was coming from: vue-router — Uncaught (in promise) Error: Redirected from "/login" to "/" via a navigation guard
Imagine you have routes a, b, and c. The user is on route a. They then click a link that is supposed to send them to route b. We then catch that in a route guard and redirect them to route c. This makes sense to the developer because there was a reason to redirect to route c, but the people in charge have decided that is a jarring experience to the user, and we shouldn't call route redirects in ways that don't make sense to the user.
tl;dr the problem is with how you initially direct to a route. Instead of router.push('/b') do router.push('/b').catch((e) => e); Just suppress the error and everything will work great.

Angular lazy loaded module with child component default route

Angular 9. Child component is not loaded with lazy loaded module component. Here is my code.
app-routing.module.ts
{
path: '',
redirectTo: '/auth',
pathMatch: 'full'
},
{
path: 'auth',
loadChildren: () => import('./pages/auth/auth.module').then(m => m.AuthModule),
}
auth.module.ts
{
path: '',
component: AuthRootComponent,
children: [
{ path: 'login', component: LoginComponent},
{ path: 'forgot', component: ForgotPasswordComponent},
{ path: '', redirectTo: 'login', pathMatch: 'full' },
]
}
When i use localhost:4200/ it redirects me to localhose:4200/auth. It does not load login component.
But when i hit url in browser (localhost:4200/auth) it will load login component, and new url will be desired url which is localhost:4200/auth/login.
Please tell me, why it does not load login from child array when auth module is loaded and the url is localhost:4200?
URL should be localhost:4200/auth/login but right now getting url localhost:4200/auth
I think you'll have to change from redirectTo: '/auth' to redirectTo: 'auth'. That is because when you're using absolute redirects(e.g /auth), only one redirect operation will be allowed, in this case the one which goes to /auth, meaning that any other redirects(e.g redirectTo: 'login') will be ignored.
Here's where it checks if there were any absolute redirects:
if (allowRedirects && this.allowRedirects) {
return this.expandSegmentAgainstRouteUsingRedirect(
ngModule, segmentGroup, routes, route, paths, outlet);
}
And here is where it states that after an absolute redirect, there can't be any other redirects:
if (e instanceof AbsoluteRedirect) {
// after an absolute redirect we do not apply any more redirects!
this.allowRedirects = false;
// we need to run matching, so we can fetch all lazy-loaded modules
return this.match(e.urlTree);
}
Absolute vs Relative Redirects
Excerpt from my GH repo.
The difference between redirectTo: 'foo/bar' and redirectTo: '/foo/bar' is that:
/ - will start again from the root(the first outermost array of routes)
`` - will start the search from the first route from the array this current route resides in

Use same component with multiple routes, depending on path component type

I am trying to use one component for multiple routes, but depending on the type of the path component a different action has to be performed. I have three main views:
LevelOneView: This is the landing page, and only shows a search widget. Once the query has been setup, it redirects to the /search path and stores the query in VueX
LevelTwoView: This view shows the result of the query. Once the user clicks on the result, it brings them to the /:id path
LevelThreeView: This view shows the details of the listing
These three views need to share some of the paths. The idea is the following:
/search => Go to LevelTwoView, no special work
/:city => A location (london, newyork, ...) can be inserted and it should go to the LevelTwoView with the query already set to search only listings available in that region. The url should be /search
/:id => Go to LevelThreeView using the id as a parameter for the fetch req. This is a number
I set up a beforeEnter guard on the /:city route so that it checks if the path param is a number, and in that case it should redirect to the LevelThreeView. This works fine if I am going from the /search path to the /:id (or even directly to the /:id path from the outside), but if I am going from a LevelThreeView to a LevelThreeView it does not work and returns this error:
Uncaught (in promise) undefined
eval # vue-router.esm.js?8c4f:2051
abort # vue-router.esm.js?8c4f:2082
eval # vue-router.esm.js?8c4f:2131
beforeEnter # router.js?41cb:43
iterator # vue-router.esm.js?8c4f:2120
step # vue-router.esm.js?8c4f:1846
eval # vue-router.esm.js?8c4f:1847
eval # vue-router.esm.js?8c4f:2139
eval # main.js?56d7:115
iterator # vue-router.esm.js?8c4f:2120
step # vue-router.esm.js?8c4f:1846
step # vue-router.esm.js?8c4f:1850
runQueue # vue-router.esm.js?8c4f:1854
confirmTransition # vue-router.esm.js?8c4f:2147
transitionTo # vue-router.esm.js?8c4f:2034
push # vue-router.esm.js?8c4f:2365
eval # vue-router.esm.js?8c4f:2779
push # vue-router.esm.js?8c4f:2778
goToListing # ListingCard.vue?6c5e:147
click # ListingCard.vue?917d:18
invokeWithErrorHandling # vue.runtime.esm.js?2b0e:1854
invoker # vue.runtime.esm.js?2b0e:2179
original._wrapper # vue.runtime.esm.js?2b0e:6911
I set up the router in the following way:
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'LevelOne',
component: LevelOneView,
beforeEnter: (to, from, next) => {
// Clear the query
store.commit('resetQuery');
return next();
}
},
{
path: '/search',
name: 'LevelTwo',
component: LevelTwoView
},
{
path: '/:city',
name: 'LevelTwoWithCity',
component: LevelTwoView,
beforeEnter(to, _, next) {
// Strip the leading slash
let toPath = to.path.replace("/", "").toLowerCase();
// Check if the path is a number and push the lv3 view
if (!isNaN(toPath)) {
console.log("Router: found valid path for lv 3")
const id = parseInt(toPath);
console.log(id);
return next({name: 'LevelThree', params: { id: id }});
}
// Load up the query
let query = store.getters.getQuery;
// Get the name of all the regions, as a list, to lower case
let regions = query.regions.map(el => el.name.toLowerCase());
// Check if we have a matching one
if (regions.includes(toPath)) {
// First, deactivate them all
query.regions.map(el => el.value = false)
// Then, activate only the one that matched
query.regions.filter(el => el.name.toLowerCase().includes(toPath)).map(el => el.value = true);
// Commit the query
store.commit('setQuery', query);
}
return next();
}
},
{
path: '/:id',
name: 'LevelThree',
component: LevelThreeView,
beforeEnter(to, from, next) {
console.log("Inside Level3 beforeEnter");
next();
}
},
{
path: '/not-found',
name: '404',
component: NotFound,
},
{
path: '*',
name: '404',
component: NotFound,
},
]
})
I am a beginner at this so I might be making a trivial mistake. If anyone can give me any pointers it would be greatly appreciated.
Thanks!
In the end, I have been able to find a "workaround". For each of the routes, you can define aliases. So I changed the code to use them, and load up all the possible routes at start up:
// Generate the aliases
let cities = myCities.map(el => "/" + el.name.toLowerCase());
// Get the base case (i.e. the first element to have something to match against)
let baseCity = cities.splice(0, 1)[0];
export default new Router({
...
routes: [
{
path: '/',
...
},
{
path: '/search',
...
},
{
path: baseCity,
name: 'LevelTwoWithCity',
component: LevelTwoView,
alias: cities,
... // Here I set up the query object to be passed on
},
{
path: '/:id',
name: 'LevelThree',
component: LevelThreeView
},
{
path: '*',
name: '404',
component: NotFound,
},
]
})
in theory, this could be implemented dinamically using the addRoutes() method described here

TypeError: e is not a function

Good day.
When running my app i get the following error related to my vue-router:
TypeError: e is not a function
however i have nothing ... i simply named "e" in my code.
I have a few before each options but nothing too big besides a few cookies deletions.
I have a few imports of apps and i am trying to use them in my router and they all work. So does the cookies. I did the beforeEach() method a few times to see if the error was there but so far no luck.
i got no idea of what is going on.
EDIT: When trying to figure this i was comenting on my code to see if i could find the error and when i removed most of the beforeEach() section i left on the next() function and a new error showed sayin "t is not a function", so i guess for some reason java script is only recognizing the last letters of my funcions, like t in next() and e in some().
EDIT2: After removing unecessary code that i copied from another project apperently the error happens in my next() function.
here's my router code:
import Vue from "vue";
import Router from "vue-router";
import Dashboard from "#/views/Dashboard.vue";
import Opcoes from "#/views/settings.vue";
import LoginForm from "#/components/component/loginForm.vue";
import store from "./store.js";
import Cookies from "js-cookie";
Vue.use(Router);
let router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "loginForm",
component: LoginForm,
meta: {
guest: true
}
},
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
meta: {
requiresAuth: true
}
},
{
path: "/opcoes",
name: "Opcoes",
component: Opcoes,
meta: {
requiresAuth: true
}
},
{
path: "/sair",
name: "Sair",
component: LoginForm,
meta: {
requiresAuth: true
}
}
]
});
router.beforeEach((to, from, next) => {
const expirationDate = Cookies.get("expireIn");
const now = new Date().getTime();
if (now >= expirationDate) {
Cookies.remove("expirationDate");
Cookies.remove("token");
Cookies.remove("userId");
store.dispatch('LOGOUT');
} else {
next();
}
});
export default router;
Heres a print of my stack trace:
My Big Fat Guess
The error message points to line 2203 (line 1921 is part of a generic error handler). This line is in the Router push() method
onComplete && onComplete(route);
My guess is, somewhere in your code not shown in your question (perhaps in your LoginForm component), you are calling this.$router.push() with a second argument that is not a function, eg
this.$router.push({ name: 'Dashboard' }, somethingNotAFunction)
Other problems (from previous versions of the question)
Your / route (loginForm) has no params so this...
next({
path: "/",
params: { nextUrl: to.fullPath }
});
is invalid. Did you perhaps mean to send nextUrl as a query parameter. If so, use
next({ name: 'loginForm', query: { nextUrl: to.fullPath } })
You also have
next({ path: "login" });
and
next({ name: "login" });
neither of which exist in your routes. You should only forward the route request to existing routes.
Finally, if you're using Vuex, you should not be directly assigning state values. In strict-mode, this
store.state.isLoggedIn = false
will trigger an error. You should be committing state mutations instead.