Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redirecting twice in a single Vue navigation

In my Vue app, a user's homepage depends on their role. To ensure that a user is shown the correct homepage, I use this navigation guard:

export default (to, from, next) => {
  const authService = getAuthService()

  if (to.path === '/') {
    // if they've requested the home page, send them to 
    // different pages depending on their role
    if (authService.isUser()) {
      next({ name: 'events' })

    } else if (authService.isAdmin()) {
      next({ name: 'admin-events' })

    } else {
      next()
    }
  }
}

Then when a user successfully logs in, I redirect them to '/'

  this.$router.push({path: '/'))

and the nav guard above redirects them to their role-specific homepage. However, redirecting twice in the course of a single navigation action is not allowed and causes the following error to appear in the console when the second redirection occurs (in the nav guard) `

Uncaught (in promise) Error: Redirected when going from "/login" to "/" via a navigation guard.

Another case in my app where this happens is the 404 component that handles attempts to access non-existent routes, i.e.

  1. User attempts to access invalid route
  2. 404 component redirects back to '/'
  3. Nav guard redirects to user-specific homepage, causing an error

Is there a way I can support these use cases without redirecting twice?

like image 749
Antonio Dragos Avatar asked Jun 22 '20 10:06

Antonio Dragos


Video Answer


1 Answers

tldr: vm.$router.push(route) is a promise and needs to .catch(e=>gotCaught(e)) errors.


This will be changed in the next major@4


Currently@3 errors are not distinguished whether they are NavigationFailures or regular Errors.

The naive expected route after vm.$router.push(to) should be to. Thus one can expect some failure message once there was a redirect. Before patching router.push to be a promise the error was ignored silently. The current solution is to antipattern a .catch(...) onto every push, or to anticipate the change in design and wrap it to expose the failure as result.

Future plans have it to put those informations into the result:

  let failure = await this.$router.push(to);
  if(failure.type == NavigationFailureType[type]){}
  else{}

Imo this error is just by design and should be handled:

hook(route, current, (to: any) => { ... abort(createNavigationRedirectedError(current, route)) ...}

So basically if to contains a redirect it is an error, which kinda is equal to using vm.$router.push into a guard.

To ignore the unhandled error behaviour one can pass an empty onComplete (breaks in future releases):

vm.$router.push(Route, ()=>{})

or wrap it in try .. catch

try {

  await this.$router.push("/")
} catch {

}

which prevents the promise to throw uncaught.


to support this without redirecting twice means you put the guard to your exit:

let path = "/"
navguard({path}, undefined, (to)=>this.$router.push(to||path))

which will polute every component redirecting to home


btw the router-link component uses an empty onComplete


Assumption that redirecting twice is not allowed is wrong.

like image 86
Estradiaz Avatar answered Oct 18 '22 09:10

Estradiaz