Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 211
Kyll Avatar asked Jul 24 '18 09:07

Kyll


3 Answers

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.

like image 85
Kyll Avatar answered Oct 30 '22 21:10

Kyll


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();
  }
})
like image 4
Jake Dolan Avatar answered Oct 30 '22 19:10

Jake Dolan


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 :)

like image 3
Ala Baganné Avatar answered Oct 30 '22 20:10

Ala Baganné