Vue-router: router.app.$data undefined in beforeEach on first load

Created on 26 Mar 2019  路  7Comments  路  Source: vuejs/vue-router

Version

3.0.2

Reproduction link

https://jsfiddle.net/q574co12/

Steps to reproduce

Run the minimal reproduction link, check console output

What is expected?

Browser shows "Hello"
Console output: "Joe"

What is actually happening?

Browser shows "Please log in"
Console output: undefined


In the example I want to check if someone is logged in and otherwise redirect to a login route. However, this solution always redirects to the login route, even if someone is logged in, because the loggedInUser is not available in router.app.

Most helpful comment

Apart from the drawback you mentioned. This could cause other problems when users are pushing in created or before the router is initialized. It also makes the $route initial value to be different for one tick even with no guards. I'm afraid this could break some users applications so we can't do it

For your use case, the solution is quite simple, you need to wait for Vue to render once:

router.beforeEach(async (to, from, next) => {
    await Vue.nextTick()
    console.log(router.app.loggedInUser);
  if (to.path != '/login' && !router.app.loggedInUser) {
    next('/login');
    return;
  }
  next();
})

That way, you are sure the data will be preset and it's explicitly keeping $route as its initial value at the beginning.

In larger applications, I would create an action at the Store level that returns a promise when the data has been read and await the action call in the guard

All 7 comments

that's because the router is initialized in the root instance's beforeCreatehook, At that moment, the $data for the root instance hasn't been set yet.

Is that configurable? Or better... can I just disable the automatic initialization and just start the router in mounted() of my root component?

No and no, sorry.

How do you get the login informarion into data before starting the app anyway? Do you hardcode it from a server template or something?

Either way there would be other solutions. For discussing that, we have forum.vuejs.org - meet me there.

I just got the next use case for this. Under certain circumstances (when the password has been reset by an administrator) when a user logs in, I want to redirect them to the "change password" page to force them to set a new password.

On a page reload, I fill the information whether the password was recently reset from window.password_was_reset into my root instance via the data function and I'm doing the redirect in a navigation guard (where else?).

Because of this issue I either have to write code to always keep window.password_was_reset and $root.password_was_reset in sync or I have to write code in the navigation guard that checks if it's a page reload and then looks in window.password_was_reset and otherwise in $root.password_was_reset.

Either way seems super messy and it's just because the router is initialized too early.

I changed this to created() locally and it works great, with the drawback that watchers cannot use this.$route, because apparently watchers are created and evaluated before created().

It would be nice if the official version had this much less surprising behavior.

Apart from the drawback you mentioned. This could cause other problems when users are pushing in created or before the router is initialized. It also makes the $route initial value to be different for one tick even with no guards. I'm afraid this could break some users applications so we can't do it

For your use case, the solution is quite simple, you need to wait for Vue to render once:

router.beforeEach(async (to, from, next) => {
    await Vue.nextTick()
    console.log(router.app.loggedInUser);
  if (to.path != '/login' && !router.app.loggedInUser) {
    next('/login');
    return;
  }
  next();
})

That way, you are sure the data will be preset and it's explicitly keeping $route as its initial value at the beginning.

In larger applications, I would create an action at the Store level that returns a promise when the data has been read and await the action call in the guard

Works like a charm. :)

Was this page helpful?
0 / 5 - 0 ratings