Reputation: 1763
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
Reputation: 63089
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...');
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
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