Search code examples
javascriptvue.jsvuejs3nuxt.jsnuxt3.js

Issues with useRoute in middleware


When using a composable which uses useRoute inside middleware I receive this warn: '[nuxt] Calling useRoute within middleware may lead to misleading results. Instead, use the (to, from) arguments passed to the middleware to access the new and old routes.'

I tried the solutions from here: https://github.com/nuxt-modules/i18n/issues/2064 and here Nuxt i18n Calling useRoute within middleware may lead to misleading results, but they didn’t work for me. I’m trying to understand whether this is a bug that I should wait to be fixed or if there’s an actual solution. I’m using useRoute inside useStoreAuth(), but not inside checkSession. Since I added useStoreAuth to the middleware, it says I can’t use useRoute. Creating a separate composable for the signIn function where I use useRoute won’t work, because I need to access the signedIn ref from useStoreAuth in both checkSession and signIn functions. Besides, I need to use checkSession within signIn, which means if I create a separate composable called useStoreSession and add it to the middleware, it will also contain useStoreAuth with useRoute.

export default defineNuxtRouteMiddleware(async (to, _from) => {
    const { signedIn, checkSession } = useStoreAuth();

    await checkSession();

    if (!signedIn.value && to.path.startsWith('/profile')) {
        return navigateTo(`/sign-in?redirect=${encodeURIComponent(to.path)}`);
    }
    if (signedIn.value && to.path === '/sign-in') {
        return navigateTo('/');
    }
});

The way I use useRoute inside a composable:

export const useStoreAuth = () => {
    const route = useRoute();
    const signIn = async (signInData: SignInData) => {
        const { apiBase } = useRuntimeConfig().public;
        state.pending = true;
        state.error = null;
        state.successMessage = null;

        const res = await fetch(`${apiBase}/signIn`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(signInData),
        });

        try {
            if (res.ok) {
                await checkSession();
                if (state.signedIn) {
                  if (route.query.redirect) {
                      await navigateTo(`${route.query.redirect}`);
                  } else {
                      await navigateTo(`${route.query.redirect}` || '/');
                  }

                  state.successMessage = 'Signed in successfully';
              } else {
                    state.error = 'Error signing in';
                }
            } else if (res.status === 400) {
                state.error = 'Validation error';
            } else {
                const resData = await res.json();
                state.error = resData.message || "User doesn't exist";
            }
        } catch (err) {
            state.error = `An unexpected error occurred ${err.message}`;
        } finally {
            state.pending = false;
        }
    };


    return {
        ...toRefs(state),
        signIn,
    };
};

Looking for a possible solution.


Solution

  • In order to reliably use composables in Pinia store it should be guaranteed to be initialized at the time when they are allowed, i.e. the first useStore() call should happen in component's setup, not earlier. This is not feasible if a store is used in other places such as router hooks, the first navigation can occur in Vue before component instance is created. In case of Nuxt's useRoute the warning explain the problem; even if it's allowed, the value is ambiguous during the navigation.

    A more practical approach is to inject a store with composable result at the moment when a composable is accessible, e.g. in root component:

    store.route = useRoute()
    

    In this case this is the solution because only checkSession is used in router hook. As long as it doesn't use route, it doesn't matter if the latter is not initialized at this point. This would be a problem if route is used prior to this, and a possible workaround would be to allow a method that uses it (signIn) to optionally accept route object via a parameter. So it could be used as store.signIn(data, to) in router hook, and signIn could use it as (route.value ?? routeArg).query.redirect. Again, not a problem here, but that's how it would be approached if it were.

    This also indicates a possible design problem, a store contains routing logic that could belong to a router. In this case sign-in route and router hooks could handle redirect parameter and possible redirects, and root component could react to the changes in store.signedIn state, while the store would be responsible for the logic that isn't tied to specific route implementation.