route.beforeEnter fetch state issue

I’m trying to use the global beforeEnter guard in the router config to ensure that data is being cleared and fetched appropriately before each route change. This means the router can’t proceed to the TO route until the data is fetched. To address this, I’m flipping a toggle in vuex before and after the fetch starts and completes to replace the router-view with a loading component.

The problem is that the route-view is correctly hidden
The fetch completes
The route-view shows with the FROM route
Then the route-view proceeds to the TO route
The flicker is fairly significant

I think ultimately I need to refactor our approach but in the meantime, can you help me figure out what I’m doing wrong? Simplified code:

//MyApp.vue
<my-loader
    v-if="appLoading"
></my-loader>
<router-view
    v-if="!appLoading"
></router-view>

//routerConfig
...
function getData(to, from, next) {
    store.commit('appLoading', true);
    store.dispatch('getData')
        .then(() => {
            next();
            store.commit('appLoading', false);
        });
}
    
...
{
    path: 'myChild',
    name: 'myChild',
    component: () => import('/MyChild.vue'),
    beforeEnter: getData,
}

Can anyone help me figure out what I’m missing?

I imagine this is because the router-view has to remount. Instead of using v-if, try using v-show.

<my-loader
    v-show="appLoading"
></my-loader>
<router-view
    v-show="!appLoading"
></router-view>

Hmm… Nope. Still a flash. I’ve tried console logging the time of the next call and the state of the appLoading and everything seems to be triggering in order.

I have a workaround, so it’s not super urgent, but I guess I’m wondering if it’s possible that next() happens asynchronously and is initiating the move to the new component first as I entend, but that process happens slower than the newly computed appLoading. So the DOM is being updated before the component is dismounted?

Sounds logical, a state change to Vuex is going to be very quick. Not sure if next() returns a promise, but you could try and await it.

.then(async () => {
  await next();
  store.commit('appLoading', false);
});

I’ll try that now. If that doesn’t work, my workaround is putting it in the component beforeRouteEnter

No dice. I don’t think next returns anything?

Anyway, I’ll use my workaround for now, but I appreciate your help @JamesThomson . At least it sounds like I’m not totally off base on my debugging

Ah, damn. Sorry mate. I’m sure there’s a way, but just don’t have the time to go through it in depth. Good luck!

It’s cool. Like I said we have a workaround and I think this may stem from our bad pattern anyway. Thanks!

Just to be a good community member, here’s the workaround we added to every component file that gets loaded in the router-view (after the router-config hooks)

beforeRouteEnter(to, from, next) {
	next((vm) => {
		vm.$store.commit('appLoading', false);
	});
},

Just a side note for those that may come across this, a mixin would be pretty ideal for this kind of reuse.