How to prevent any routing before some async data (in Vuex store) has loaded?
Asked Answered
H

3

13

In my application I need some data to be loaded inside the VueX store before routing starts (for example user sessions).

An example of a race condition would be the following:

// In routes definition
{
  name: 'login',
  path: '/login',
  component: Login,
  meta: {
    goToIndexIf: () => store.getters['auth/loggedIn']
  }
}

In this situation the route guard might be executed before the user had been received from the server.

Using conditional rendering did not help as the route guards are executed with or without a <router-view v-if="storeReady"> within the rendered template.

How can I make all my routing wait on some asynchronous data?

Harve answered 24/7, 2018 at 9:45 Comment(0)
H
11

The solution is simple. Add an init or equivalent Vuex action to the relevant parts of your store.
It should return a Promise of all the requests for data that your application absolutely needs*:

init ({ dispatch }) {       // Could also be async and use await instead of return
  return Promise.all([
    dispatch('getUserSession'), // Using another action
    dispatch('auth/init'),      // In another module
    fetch('tehKittenz')         // With the native fetch API
    // ...
  ])
}

The above code can use anything that returns a Promise.

Then just create a global navigation guard in your router using beforeEach.
This guard will wait on the promise generated by a dispatch to the store.

// In your router initialization code
const storeInit = store.dispatch('init')

// Before all other beforeEach
router.beforeEach((to, from, next) => {
  storeInit.then(next)
    .catch(e => {
      // Handle error
    })
})

This way, if routing happens before the store is fully loaded the router will simply wait.
If routing happens after, the promise will already be in a fulfilled state and routing will proceed.

Don't forget to use something like conditional rendering so as not to display a blank screen while routing is waiting on the data.


*: This will prevent all routing and navigation as long as the data is being fetched. Be careful.

Harve answered 24/7, 2018 at 9:45 Comment(1)
For some reason for me the promise inside of router.BeforeEach was not working. I added the dispatched action inside of a middleware function set up and that worked.Seasickness
J
5

Since this question was first asked, vue-router (v3.5.1) has exposed the ability to check for the initial navigation to perform operations like this and run on the first route only.

Compare from to VueRouter.START_LOCATION.

import VueRouter from 'vue-router'

const router = new VueRouter({
  // ...
})

router.beforeEach((to, from, next) => {
  if (from === VueRouter.START_LOCATION) {
    // initial navigation, handle Vuex initialization/hydration.
    initalizeOrWait().then((isLoggedIn) => {
      // handle navigation or pass to guard how it fits your needs here etc.
      next();
    });
  } else {
    next();
  }
})
Juta answered 11/6, 2021 at 21:18 Comment(1)
Do you believe this should be the accepted answer instead of mine? Please comment if so. I no longer use VueRouter and lost the domain knowledge so it's your call.Harve
L
3

What i did and worked fine for me is wrapping my Vue instance (new Vue({... })) inside .then() "Promise".. this promise would resolve(null) if everything is fine and resolve an error when an errors occurs so i can render the vue instance conditionally based on the error

here i call my async function and wait until it loads the store and then initialize my app

my async function that uses the token to get me the data

doing so will allow the routes guards who work on the fetched store data to work properly

Hope that helps and sorry if my english is bad :)

Levanter answered 7/9, 2020 at 15:21 Comment(1)
The problem with this is that router initial navigation doesn't wait for an app to be mounted, this needs to be specifically handled.Buckwheat

© 2022 - 2024 — McMap. All rights reserved.