Search code examples
javascriptvue.jsvuexstate-managementpinia

How to properly install a Pinia Store?


I'm building a Vue 3 app using the OptionsAPI along with a Pinia Store but I frequently run into an issue stating that I'm trying to access the store before createPinia() is called.

I've been following the documentation to use the Pinia store outside components as well, but maybe I'm not doing something the proper way.

Situation is as follows:

I have a login screen (/login) where I have a Cognito session manager, I click a link, go through Cognito's signup process, and then get redirected to a home route (/), in this route I also have a subroute that shows a Dashboard component where I make an API call.

On the Home component I call the store using useMainStore() and then update the state with information that came on the URL once I got redirected from Cognito, and then I want to use some of the state information in the API calls inside Dashboard.

This is my Home component, which works fine by itself, due to having const store = useMainStore(); inside the mounted() hook which I imagine is always called after the Pinia instance is created.

<template>
  <div class="home">
    <router-view></router-view>
  </div>
</template>

<script>
import {useMainStore} from '../store/index'

export default {
  name: 'Home',
  components: {
  },
  mounted() {
    const store = useMainStore();

    const paramValues = {}

    const payload = {
      // I construct an object with the properties I need from paramValues
    }

    store.updateTokens(payload); // I save the values in the store
  },
}
</script>

Now this is my Dashboard component:

<script>
import axios from 'axios'
import {useMainStore} from '../store/index'

const store = useMainStore();

export default {
    name: "Dashboard",
    data() {
    return {
        user_data: null,
      }
  },
  mounted() {
    axios({
      url: 'myAPIUrl',
      headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
    }).then(response => {
      this.user_data = response.data;
    }).catch(error => {
      console.log(error);
    })
  },
}
</script>

The above component will fail, and throw an error stating that I'm trying to access the store before the instance is created, I can solve this just by moving the store declaration inside the mounted() hook as before, but what if I want to use the store in other ways inside the component and not just in the mounted hook? And also, why is this failing? By this point, since the Home component already had access to the store, shouldn't the Dashboard component, which is inside a child route inside Home have the store instance already created?

This is my main.js file where I call the createPinia() method.

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

const pinia = createPinia();

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

And the error I get is:

Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?

My Store file:

import { defineStore } from 'pinia';

export const useMainStore = defineStore('main', {
  state: () => ({
    access_token: sessionStorage.getItem('access_token') || '',
    id_token: sessionStorage.getItem('id_token') || '',
    token_type: sessionStorage.getItem('token_type') || '',
    isAuthenticated: sessionStorage.getItem('isAuthenticated') || false,
    userData: JSON.parse(sessionStorage.getItem('userData')) || undefined
  }),
  actions: {
    updateTokens(payload) {
      this.id_token = payload.id_token;
      this.access_token = payload.access_token;
      this.token_type = payload.token_type

      sessionStorage.setItem('id_token', payload.id_token);
      sessionStorage.setItem('access_token', payload.access_token);
      sessionStorage.setItem('token_type', payload.token_type);
      sessionStorage.setItem('isAuthenticated', payload.isAuthenticated);
    },
    setUserData(payload) {
      this.userData = payload;
      sessionStorage.setItem('userData', JSON.stringify(payload));
    },
    resetState() {
      this.$reset();
    }
  },
})

Solution

  • It's possible but not common and not always allowed to use use composition functions outside a component. A function can rely on component instance or a specific order of execution, and current problem can happen when it's not respected.

    It's necessary to create Pinia instance before it can be used. const store = useMainStore() is evaluated when Dashboard.vue is imported, which always happen before createPinia().

    In case of options API it can be assigned as a part of component instance (Vue 3 only):

      data() {
        return { store: useMainStore() }
      },
    

    Or exposed as global property (Vue 3 only):

    const pinia = createPinia();
    const app = createApp(App).use(router).use(pinia);
    app.config.globalProperties.mainStore = useMainStore();
    app.mount('#app');