I can't access my routes from the store Pinia Vuejs3

Viewed 1690

I can't access my routes from the store. There may be a good explanation for this. I use Vuejs3 and Pinia

My store :

import {defineStore} from 'pinia'
import {useRoute} from "vue-router";


type navigationState = {
    selectedNavigationItem: INavigationItem | null,
    selectedNavigationPage: INavigationPage | null,
}

export const useNavigationStore = defineStore('navigationStore', {
    state: () => ({
        /**
         * when the user clicks on an element of the navbar we store the navigation item here
         */
        selectedNavigationItem: null,
        /**
         * when the user clicks on an element of the sidebar we store the navigation page here
         */
        selectedNavigationPage: null,
    } as navigationState),
    actions: {

        /**
         * Set Selected navigation page
         * @param navigationPage
         * @type INavigationPage
         */
        setSelectedNavigationPage(navigationPage: INavigationPage | null) {
            console.log(useRoute())
            this.selectedNavigationPage = navigationPage
        },
    },
})

when I do a console log like in the method setSelectedNavigationPage

I have an undefined

3 Answers

useRoute and useRouter must be used in Vue components and specifically setup method or inside script setup.

useRouter Docs

useRoute Docs


If you want to access the router though, you can simply import it:

router-file

import { createRouter, createWebHistory } from 'vue-router'

export const router = createRouter({
  history: createWebHistory(),
  routes: [/* ... */]
})

then in your pinia store you can import and use the router from that file:

import { defineStore } from 'pinia'
import router from './router'

export const myStore = defineStore('myStore', () => {
  // router.push
  // router.replace
})

You can add router as Pinia plugin

// src/main.js

import { createPinia } from 'pinia'
import { createApp, markRaw } from 'vue'
import App from './App.vue'
import Api from './api.js' // my axios wrapper

const app = createApp(App)
const pinia = createPinia()
pinia.use(({ store }) => {
  store.router = markRaw(router)
  store.api = markRaw(Api)
})

app
  .use(pinia)
  .use(router)
  .mount('#app')

Then router and api are available on this

// src/stores/user.js

import { defineStore } from 'pinia'

export const useUserStore = defineStore('User', {
  state: () => ({}),
  actions: {
    async login() {
      await this.api.POST('login', {username, password})
      this.router.replace({name: 'home'})
    }
  }
})

Note that you can't call this.router with arrow function.

login: async () => {
  this.router.replace({name: 'home'}) // error
}

For typescript user, to correctly get type for this.router and this.api:

// src/global.d.ts
import { Router } from 'vue-router'
import Api from './api'

export { }

declare global {

}

declare module 'pinia' {
  export interface PiniaCustomProperties {
    router: Router,
    api: typeof Api
  }
}

I found this way on pinia github. https://github.com/vuejs/pinia/discussions/1092

But I still don't know how to add this.route to Pinia.

Future reader, please comment if you know how to do it.

EDIT: Thanks for sophiews for pointing this out.

Just found out that we have different way to defineStore: Setup Stores

// src/stores/user.js

import { defineStore } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import api from './api.js'

export const useUserStore = defineStore('User', () => { // use function
  const route = useRoute()
  const router = useRouter() // `this.router` won't work here
  
  const login = async () => {
    await api.POST('login', {username, password}) // `this.api` won't work either
    router.replace({name: 'home'})
  }

  return { login } // need to return anything we need to expose
})

You could wrap the process of instantiating a store within a factory/function, this will allow you to expand the stores capabilities regarding your custom needs. Below you can see that we can instantiate a store referencing the urql client and the router object. Have a look:

    export class StoreManager {
  static _instances: any[] = [];
  public static spawnInstance(
    id: string,
    storeType?: EStoreType,
    clientHandle?: ClientHandle,
    routerHandle?: Router,
  ) {

    if (StoreManager._instances.find((i) => i.id === id)) {
      const store = StoreManager._instances.find((i) => i.id === id).instance;
      return store;
    } else {
      const store = StoreManager.initStore(
        id,
        storeType,
        clientHandle ?? null,
        routerHandle ?? null,
      );
      StoreManager._instances.push({
        id: id,
        instance: store,
        storeType: storeType,
      });
      return store;
    }
  }

  public static initStore(
    id: string,
    storeType: EStoreType,
    clientHandle: ClientHandle | null,
    routerHandle: Router | null,
  ) {
    const baseState = {
      _meta: {        
        storeType: storeType,
        isLoading: true,        
      },
      _client: clientHandle,
      _router: routerHandle,
    };

    const baseActions = {
      async query(query: any, variables: any[] = []) {
        // use urql client
        
      },
    };
    const baseGetters = {
      storeType: (state) => state._meta.storeType,
      getCurrentRoute: (state) => {
        if (!state._router) {
          throw new RouterNotSetException(
            `This store does not have a router set up`,
          );
        }
        return state._router.currentRoute.fullPath.replace('/', '');
      },
    };

    switch (storeType) {
      case EStoreType.DEFAULT:
        return defineStore({
          id: `${id}`,
          state: () => ({
            ...baseState,
          }),
          actions: {
            ...baseActions,
          },
          getters: {
            ...baseGetters,
          },
        });

      default:
        throw new StoreTypeNotFoundException(
          `Expected valid 'EStoreType', got ${storeType}`,
        );
    }
  }
}

Within your VueComponent a store instance would be spawned like this:

const store = StoreManager.spawnInstance(
  uuidv4(),
  EStoreType.DEFAULT,
  useClientHandle(),
  useRouter(),
)();

Related