Vue: Can't access Pinia Store in beforeEnter vue-router

Viewed 11297

I am using Vue 3 including the Composition API and additionally Pinia as State Management.

In the options API there is a method beforeRouteEnter, which is built into the component itself. Unfortunately this method does not exist in the composition API. Here the code, which would have been in the beforeRouteEnter method, is written directly into the setup method. However, this means that the component is loaded and displayed first, then the code is executed and, if the check fails, the component is redirected to an error page, for example.

My idea was to make my check directly in the route configuration in the beforeEnter method of a route. However, I don't have access to the Pinia Store, which doesn't seem to be initialized yet, although it is called before in the main.js.

Console Log

Uncaught Error: []: getActivePinia was called with no active Pinia. Did you forget to install pinia?
    const pinia = createPinia()
    app.use(pinia)
This will fail in production.

Router.js

import { useProcessStore } from "@/store/process";

const routes: Array<RouteRecordRaw> = [
{
    path: "/processes/:id",
    name: "ProcessView",
    component: loadView("ProcessView", "processes/"),
    beforeEnter: () => {
      const processStore = useProcessStore();
      console.log(processStore);
    },
    children: [
      {
        path: "steer",
        name: "ProcessSteer",
        component: loadView("ProcessSteer", "processes/")
      },
      {
        path: "approve/:code",
        name: "ProcessApprove",
        component: loadView("ProcessApprove", "processes/")
      }
    ]
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

export default router;

main.js

import { createApp } from "vue";
import "@/assets/bundle-bootstrap.css";
import App from "@/App.vue";
import { createPinia } from "pinia";
import router from "@/router";
import SvgIcon from "@/components/SvgIcon.vue";

const pinia = createPinia();
const app = createApp(App);

app.use(pinia);
app.use(router);
app.component("SvgIcon", SvgIcon);

router.isReady().then(() => {
  app.mount("#app");
});

5 Answers

However, I don't have access to the Pinia Store, which doesn't seem to be initialized yet, although it is called before in the main.js

Before what? Pinia instance is created with const pinia = createPinia(); after the router module is imported - while it is imported, all side-effects including the call to createRouter() are executed. Once the router is created it begins it's initial navigation (on client - on server you need to trigger it with router.push()) - if you happen to be at URL matching the route with guard that is using Pinia store, the useProcessStore() happens before Pinia is created...

Using a store outside of a component

You have two options:

  • either you make sure that any useXXXStore() call happens after Pinia is created (createPinia()) and installed (app.use(pinia))
  • or you pass the Pinia instance into any useXXXStore() outside of component...
// store.js
import { createPinia } from "pinia";

const pinia = createPinia();

export default pinia;
// router.js
import pinia from "@/store.js";
import { useProcessStore } from "@/store/process";

const routes: Array<RouteRecordRaw> = [
{
    path: "/processes/:id",
    name: "ProcessView",
    component: loadView("ProcessView", "processes/"),
    beforeEnter: () => {
      const processStore = useProcessStore(pinia ); // <-- passing Pinia instance directly
      console.log(processStore);
    },
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

export default router;
// main.js
import { createApp } from "vue";
import App from "@/App.vue";
import store from "@/store.js";
import router from "@/router";

const app = createApp(App);

app.use(store);
app.use(router);

router.isReady().then(() => {
  app.mount("#app");
});

Hope this would be helpful. Vue provide support for some functions in which we need store(outside of the components).

To fix this problem I just called the useStore() function inside the function provided by Vue(beforeEach) and it worked.

Reference : https://pinia.vuejs.org/core-concepts/outside-component-usage.html

Example :

import { useAuthStore } from "@/stores/auth";
.
.
.
.
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

router.beforeEach(async (to, from) => {
  const authStore = useAuthStore();

  // use authStore Here
});

You can pass the method in the second parameter of definestore:

store.js

export const useAppStore = defineStore('app', () => {
  const state = reactive({
    appName: 'App',
    appLogo: ''
  })

  return {
    ...toRefs(state)
  }
})

router.js

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

I have resolved this by adding lazy loading

const routes = [
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

I have same problem to access the store in "beforeEach" method for managing authorization.

I use this method in main.js, not in router.js. in router.js store is not accessible.

create pinia instance in piniCreate.js

//piniaCreate.js
import { createPinia } from "pinia";
const pinia = createPinia();
export default pinia;

after that create my store in mainStore.js

import { defineStore } from 'pinia'
export const mainStore = defineStore('counter', {
    state: () => {
        return {
            user: {
                isAuthenticated: isAuthen,
            }
        }
    },
    actions: {
        login(result) {
            //...
            this.user.isAuthenticated = true;
        }  , 
        logOff() {
            this.user.isAuthenticated = false;
        }
    }
});

Then I used beforeEach method in the main.js

//main.js
import { createApp } from 'vue'
import App from './App.vue'
import pinia from "@/stores/piniaCreate";
import { mainStore } from '@/stores/mainStore';

import router from './router'

const app = createApp(App)
    .use(pinia)
    .use(router)
const store1 = mainStore();

router.beforeEach((from) => {
    if (from.meta.requiresAuth && !store1.user.isAuthenticated) {
        router.push({ name: 'login', query: { redirect: from.path } });
    }
})
app.mount('#app');
Related