LuMa
LuMa

Reputation: 1763

Execute Vuex action before initial routing

I have setup Vuex and Vue Router. I want to show a setup page, in case a users account isn't properly setup. This is implemented by making an API call to the backend. The result is stored inside Vuex and accessed using a getter. The action is dispatched in beforeCreate in the root Vuex instance:

new Vue({
  router,
  store,
  render: h => h(App),

  beforeCreate() {
    this.$store.dispatch("config/get");
  }
}).$mount("#app");

However, my router beforeEach never receives true but the getter is definitely returning true as per DevTools:

router.beforeEach((to, next) => {
  if (!to.matched.some(record => record.meta.requiresSetup)) {
    next();
    return;
  }

  if (!store.getters["config/isConfigured"]) { // Executed every time.
    next("/setup");
    return;
  }

  next();
});

Upvotes: 2

Views: 3723

Answers (2)

Dan
Dan

Reputation: 63089

Delaying app load

It's not possible to use a lifecycle hook to delay loading even if the hook is marked async and performs an await for some async operation. Hooks aren't intended to allow manipulation, only to give access to the stages.

Still, you can delay the app just by not loading it until the store action completes, but realize this means a blank page for the user, which is a bad user experience. But here's how you can do that:

main.js

const preload = async () => {
  await store.dispatch('config/get');    // `await` store action which returns a promise
  
  new Vue({
    store,
    router,
    render: (h) => h(App)
  }).$mount("#app");
}

preload();
console.log('LOADING...');

The better way

It's better to dispatch the action and not wait for it. Let App.vue load and use v-if to show a splash screen until some store state isLoading is false:

main.js

store.dispatch('config/get');

new Vue({
  store,
  router,
  render: (h) => h(App)
}).$mount("#app");

App.vue

<template>
<div>
  <div v-if="isLoading">
    LOADING...     <!-- Attractive splash screen here -->
  </div>
  <div v-else>
    <router-view></router-view>  <!-- Don't show the router view until ready -->
  </div>
</div>
</template>

Remove the navigation guard completely and in App.vue, put a watch on isLoading. Once it's no longer loading, redirect based on the state of the account getter:

computed: {
  ...mapState(['isLoading'])
},
methods: {
  redirect() {
    const path = this.$route.path;
    if (!this.$store.getters["config/isConfigured"]) {
      path !== '/setup' && this.$router.push({ path: '/setup' });
    } else {
      path !== '/' && this.$router.push({ path: '/' });
    }
  }
},
watch: {
  isLoading(val) {
    if (val === false) {
      this.redirect();
    }
  }
}

Upvotes: 4

Mohsin Amjad
Mohsin Amjad

Reputation: 1209

These lifecycle callbacks have synchronous behaviour. what you can do is something like

store.dispatch("config/get").then(() => {

  router.beforeEach((to, from, next) => {...}

  new Vue({...})

})

or you can call it in beforeEach of router and store some state like isLoaded and call store.dispatch only once on firstCall and for the rest of call use stored state

Upvotes: 1

Related Questions