Is it necessary to import Pinia store props multiple times within a single Vue component?

I'm currently diving into my first Vue project after working with React and vanilla JavaScript. I'm still trying to grasp a few concepts in Vue, such as importing state and action props from a Pinia store. It seems like I have to import these multiple times within a single Vue component, which is different from how it works in React.

In this scenario, I am bringing in a simple count value and an increment function, attempting to utilize them in various parts of the component:

<script setup>
// Initially, I import everything in setup, where I can access props (currentCount and incrementCount) in my template:
import { storeToRefs } from 'pinia';
import { useStore } from '@/stores/store';
const { currentCount } = storeToRefs(useStore());
const { incrementCount } = useStore();
</script>

<template>
  <main>
    Current count: {{ currentCount }}
    <button @click="incrementCount">Increment</button>
  </main>
</template>

<script>
// I cannot directly use store values from setup here.
// This statement does not work:
// console.log(currentCount);

// I also face errors when importing store values here.
// I receive the following error message:
// "getActivePinia was called with no active Pinia"
// const { currentCount } = storeToRefs(useStore());

export default {
  mounted() {
    // I have to re-import store values here for them to work correctly:
    const { currentCount } = storeToRefs(useStore());
    console.log(currentCount);
  },
  watch: {
    // Surprisingly, this reference to watching "currentCount" works fine:
    currentCount() {
      // Again, I need to import store values here for them to function properly:
      const { currentCount } = storeToRefs(useStore());
      console.log(currentCount);
    },
  },
};
</script>

It's evident that to utilize store values in my template, during mount, and in a watcher (similar to React's useEffect hook), I have to import the store props three times. Is this standard practice? Is there a more straightforward way to achieve what I'm aiming for, where I only need to import props once? I want to ensure I haven't overlooked anything and that I'm following conventional practices.

Appreciate any assistance and guidance!

Answer №1

Pinia was specifically created with the Composition API in focus.
Therefore, its primary use is within the setup() function, where it should be imported only once.

If you want to utilize Pinia outside of a setup() function, there are two main approaches:

  • Within components, you can simply return it from setup() and access it in any hook/method/getter. You can either access it as this.store or spread it:
import { useStore } from '@/store'
import { toRefs } from 'vue'
            // or from '@vue/composition-api' in Vue2

export default {
  setup: () => ({ ...toRefs(useStore()) })
}
/* This allows direct availability of every state property, getter, or action
   on the current component instance. For example, `this.currentCount`.
   Alternatively, you can make the entire store accessible as `this.someStore`:

  setup: () => ({ someStore: useSomeStore() })
  // Now you can use `this.someStore` anywhere 
 */
  • A more general approach is to export the pinia instance (obtained from createPinia()) in your main.(js|ts) file, import it where needed, and then call useStore() with the pinia instance as an argument.
    This can be done anywhere, even outside of components.
    Here's a generic example:
import { pinia } from 'main.js'
import { useSomeStore } from '@/store'

const someStore = useSomeStore(pinia);

I should also mention the helper mapState provided by pinia. It allows you to selectively choose keys exposed to the current instance. For example:

import { mapState } from 'pinia'
// ...
   computed: {
    ...mapState(useSomeStore, [ 'currentCount'])
  }
// Now `this.currentCount` is available

Note: Despite its name, mapState grants access not just to state properties but also to getters and actions. Its naming aligns with similar helpers in Vuex.


An alternative broader method is to register your store globally using the plugin registration API in Vue2:

import { useSomeStore } from '@/store';
import { createPinia } from 'pinia';

const pinia = createPinia();

const someStorePlugin = {
  install(Vue, options) {
    Vue.prototype.someStore = useSomeStore(options.pinia)
  }
};

Vue.use(someStorePlugin, { pinia });

new Vue({ pinia });

Subsequently, every component in your Vue instance will have this.someStore without requiring individual imports.

Note: I haven't personally tested global store additions (and do not recommend them - minimizing globals is best practice), but I expect this implementation to function correctly.

Answer №2

For those looking to merge pinia stores with the options API, one approach is to utilize the setup() function within the options in order to invoke the useStore method:

<script>
import { useStore } from '@/stores/store';

export default {
  setup() { 
    const store = useStore();
    return {store}
  },
  watch: {
    store.currentBrightness(newVal, oldVal){
      // implement your logic here
    }
  },
  methods: {
   // access this.store within methods
  },
  mounted() {
    console.log(this.store.currentCount);
  }
}
</script>

While some may view this as an unconventional blend of composition and options API, I find it to be a effective solution for integrating pinia stores seamlessly.

Answer №3

Nechoj has shared a clever solution to managing multiple stores efficiently. By importing the necessary stores into a parent component, you can use inject to selectively add specific parts where needed. For instance, if you have route data accessed through an API that is not required throughout your application, you can call it in a parent component and then inject it into components like a drop-down menu several levels deep.

import { useUtilsStore } from "src/stores/utilsStore";
const passengerRoutes = computed(() => utilsStore.getPassengerRoutes);
provide("passengerRoutes", passengerRoutes);

In a grandchild component:

const compRoutes = inject("passengerRoutes");

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error: Unable to access property 'BOTTOM' of an object that is not defined

Hi there, I'm having trouble with this error. Can you assist me? [ERROR] TiExceptionHandler: (main) [340,3234] /ui/common/ApplicationTabGroup_Andr.js:1703 [ERROR] TiExceptionHandler: MainWallTable.insertRowBefore([0, PendingUploadView, T ...

Unable to iterate through nested arrays in Contentful mapping

I am facing an issue while trying to map further into the array after successfully retrieving data from Contentful. The error message field.fields.map is not a function keeps popping up and I can't figure out what I'm doing wrong. export defau ...

Display a specific division depending on the outcome of an Ajax request within a PHP form

My PHP form has a layout similar to this: <form> <div id="inid"> National ID: <input type="text" id="individual_nid" oninput="getIndividualName(this.value)" /> </div> <hr /> name: <div id="individua ...

Updating the names of keys within an object that includes nested child objects

I am looking to update the keys in an object that contains nested objects with similar structures. Initially, my object looks like this: objs = { "one":{ "title":"bla", "amount":5, "children":[ { "title":"bla", ...

establish expiration date for image

On my e-commerce site, users upload images to customize product items, such as adding their photo to a cake. I'm curious if there's a way to automatically delete these images after a certain expiry date. Is it possible to implement an automatic ...

Execute jquery commands after the function has finished

I am trying to implement the code below: $(":file").change(function () { if (this.files && this.files[0]) { console.log(this.files[0]); var reader = new FileReader(); ...

Is it possible to display an unordered list upon clicking a button without relying on CSS and still achieve identical visual outcomes?

Within the responsive CSS of my web application, I am attempting to display the ul element inside the "mobile header" only upon clicking the "Connect Wallet" button. I want it to appear and disappear again as soon as anything else is clicked. Is there a w ...

Rendering props conditionally in React

I am currently facing an issue with two different states retrieved from API calls: 'movieList' and 'search'. Both of them contain arrays of movies. The 'movieList' state is automatically rendered on the page to display popular ...

Transferring a variable value between functions using autocomplete and AJAX communication

I am facing difficulties with implementing autocomplete jQuery along with AJAX call. The issue arises when a user enters text in the input field, triggering an AJAX POST request to the controller which retrieves values from the database and sends them back ...

The aria-label attribute is not compatible with the Android Chrome browser when used on an input

Have you noticed that aria-label is not being read correctly on Android Chrome? For example: <input aria-label="test" type="text"> When using Android chrome, it reads "Editbox, double tap to edit, double tap to enter text". However, when using iOS ...

Using EffectComposer in conjunction with the alpha channel in three.js

Here's the code I'm working with: renderTargetParametersRGBA = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat,stencilBuffer: true }; colorTarget = new THREE.WebGLRenderTarget( SCALE * SCREEN_WIDTH, ...

Combining the Partial<CssStyleDeclaration> union type with a dictionary can lead to potential typing complications when the implicit any flag is

Using VueJS v-bind:style binding makes it possible to set CSS variables. I am attempting to create a union type that allows for the object passed to v-bind:style to retain typings for CssStyleDeclaration, while also being relaxed enough to accept an arbitr ...

What is the best way to retrieve a value from an `<input type="number">` element and incorporate it into the script section?

Check out this code snippet <html> <head> head <title>title</title> </head> <script> var val1 = parseInt(document.getElementById('input1')); function bytton() { window.alert(val1); ...

Concealing the primary div within a Vue child component

Is there a way to conceal the primary div within a Vue application created using Vue-CLI? I attempted adding the display property, but it did not solve the problem. I am attempting to hide it within my Channels component. Here is how my main component lo ...

Steps for deactivating jQuery slider control until radio button is selected:

Looking to deactivate the operation of jQuery UI sliders until a radio button is selected. Despite my efforts, I have been unsuccessful in achieving this task and could use some guidance. For reference, here is a link to the code: http://jsfiddle.net/nlem3 ...

Create a VueJS/VuetifyJS implementation inspired by the WhatsApp swipe between tabs animation

Currently, I am utilizing the VuetifyJS framework for VueJS and I am interested in replicating the swipe between tabs transition seen in WhatsApp for Android. In WhatsApp, you have the ability to swipe left or right to view a new section as you swipe. Vue ...

Whenever I change the value of a textbox using plain JavaScript, the text becomes hidden from view

I'm struggling to understand why this function is making the textbox value invisible. I attempted changing the hidden field to a visible field, but that didn't solve the issue. Would appreciate some guidance. Here's the complete page code, p ...

What could be causing my Vue component to not refresh?

Can anyone help me figure out why this component isn't re-rendering after changing the value? I'm attempting to create a dynamic filter similar to Amazon using only checkboxes. Here are the 4 components I have: App.vue, test-filter.vue, filtersIn ...

Tips for setting the scroll back to the top when switching between pages in quasar

Whenever a qlist item is clicked by the user, it redirects to another page. However, the scrolled position from the previous page is retained and not set to the top. This means that the user has to manually scroll back to the top to view the contents of th ...

Browser refusing to acknowledge jQuery/JavaScript (bootstrap)

Here is where I include the necessary jQuery libraries and my custom jQuery code: <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script> <script src= ...