How to properly install a Pinia Store?
Asked Answered
S

3

8

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();
    }
  },
})
Snazzy answered 17/2, 2022 at 18:57 Comment(5)
You didn't specify which Vue version you use, which is important. It's not a typical usage to use use... functions outside a component. "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 isn't how lifecycle works. Dashboard evaluates useMainStore when the module is imported which likely happens before pinia plugin is installed. You can see the order yourself by placing breakpoints. – Hootenanny
@EstusFlask I'm using Vue 3, and fair enough, I'm trying to understand how to make sure I use the store only after Pinia is installed, I'm calling the createPinia() method in my main.js file where my app is mounted – Snazzy
Please, update the question with actual error, it will help other users who have the same problem – Hootenanny
I'll post the answer. Can you also show '../store/index' for completeness? – Hootenanny
@EstusFlask I added it now – Snazzy
H
4

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');
Hootenanny answered 17/2, 2022 at 20:31 Comment(7)
So, just to clarify, I should avoid calling useMainStore() outside of any component instance, always either inside the data() function or other methods or hooks? – Snazzy
Yes. Composable "use" functions are intended to be used directly in setup function but can also work in data because it's executed at the same time as setup. – Hootenanny
Praise the Sun! Thanks, I'll make the changes to my app – Snazzy
"Possible" but "not always allowed", so not always possible? – Geniculate
@LeeGoddard Possible for some composable functions but not others. This is determined by the actual implementation of a composable and the place where it's used. If a function happens to run during a setup because it's indirectly called at that time, it's allowed – Hootenanny
@EstusFlask Indeed. Worth adding to the answer? – Geniculate
the pinia documentation is horrendous. – Myrtie
R
2

Since you're using Vue 3, I suggest you to use the new script setup syntax:

<script setup>
    import { reactive, onMounted } from 'vue'
    import axios from 'axios'
    import { useMainStore } from '../store'
    
    const store = useMainStore();
    
    const data = reactive({
       user_data: null
    })        
     
    onMounted (async () => {
      try {
        const {data: MyResponse} = await axios({
          method: "YOUR METHOD",
          url: 'myAPIUrl',
          headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
        })
        
        data.user_data = MyResponse

      } catch(error){
            console.log(error)
        }
    })

</script>

Using setup you can define that store variable and use it through your code.

Rimrock answered 17/2, 2022 at 19:37 Comment(2)
Thank you, I was looking for a solution with the Options API – Snazzy
Just to add, if you use Vetur extension, vscode will complain about that syntax (I checked this months ago, I don't know about now). Vue developers suggest using "Volar" extension for that matter. – Rimrock
C
0

everyone after a lot of research I found the answer to this issue, you must pass index.ts/js for const like below:

<script lang="ts" setup>
import store from '../stores/index';
import { useCounterStore } from '../stores/counter';

const counterStore = useCounterStore(store());
counterStore.increment();
console.log(counterStore.count);
</script>
Catinacation answered 4/11, 2022 at 15:0 Comment(0)

© 2022 - 2025 β€” McMap. All rights reserved.