Share global pinia store between components - vue.js

I have two vue components with own loaders, mounted into two already rendered DOM nodes:
Component A:
import { createApp } from 'vue'
import ComponentA from '#/Vue/ComponentA.vue';
import {createPinia} from 'pinia';
createApp(ComponentA).use(createPinia()).mount(document.querySelector('.c-component-a'));
Component B:
import { createApp } from 'vue'
import ComponentB from '#/Vue/ComponentB.vue';
import {createPinia} from 'pinia';
createApp(ComponentA).use(createPinia()).mount(document.querySelector('.c-component-b'));
Now, I want to load a global pinia store into multiple components:
Pinia store:
import {defineStore} from 'pinia';
export type RootState = {
foobar: number;
}
export const useGlobalApplicationStore = defineStore({
id: 'global',
state: () => ({
foobar: 100
} as RootState),
actions: {
setFoobar(payload: number): void {
this.foobar = payload;
}
},
getters: {
getFoobar(state: RootState): number {
return state.foobar;
}
}
})
If component A sets a value in this store, component B should react to changes.
Component A:
const globalApplicationStore = useGlobalApplicationStore();
setTimeout(() => {
globalApplicationStore.setFoobar(400);
}, 2000);
Output of {{globalApplicationStore.foobar}} in component A changes from 100 to 400 after 2 seconds, as expected.
Component B:
const globalApplicationStore = useGlobalApplicationStore();
Output of {{globalApplicationStore.foobar}} in component B does not change from 100 to 400.
I guess, both components loads the store as local instances.
How can I share a store between seperate mounted components?

After a long search I found out that it's pretty easy (as often...).
In my case, I use the progressive aspect of Vue.js to put apps in different places of my HTML code. Specifically, I want to populate a shopping cart icon in the header of my layout with the number of items. So I am using a App.vue for my product-app and a Basket.vue for my basket-indicator.
The simple trick is to instantiate pinia just once. Let's say you have a main.js as an entry-point of your app:
import { createApp } from "vue";
import App from "./App.vue";
import Basket from "./Basket.vue";
import {createPinia} from 'pinia';
const pinia = createPinia();
// Init App
createApp(App)
.use(pinia)
.mount("#app");
// Init Basket
createApp(Basket)
.use(pinia)
.mount("#basket");
In your App.vue you just import your stores (in my case a product store and a cart store).
<script setup>
... import components ...
import {useProductStore} from "#/stores/ProductStore";
import {useCartStore} from "#/stores/CartStore";
const productStore = useProductStore();
const cartStore = useCartStore();
productStore.fill();
</script>
<template>
... your components ...
</template>
the same in your Basket.vue:
<script setup>
import CartWidget from "#/components/CartWidget.vue";
import {useCartStore} from "#/stores/CartStore";
import {useProductStore} from "#/stores/ProductStore";
const cartStore = useCartStore();
const productStore = useProductStore();
productStore.fill();
</script>
<template>
<div class="container">
<CartWidget/>
</div>
</template>
That's it.
"pinia": "^2.0.17",
"vue": "^3.2.39"

Related

Vue3.js and Pinia: Why is my store state undefined in VueDevtools?

I am using Vue 3 with Pinia ^2.0.14. I'm importing Pinia into the app in main.ts like so:
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App).use(pinia).mount('#app')
I'm creating the store language.ts like so:
import { defineStore } from 'pinia'
export const useLanguageStore = defineStore({
id: 'language',
state: () => ({
language: 'English',
languages: ['English', 'Spanish'],
}),
})
and using it in LanguageDropdown.vue like so:
<script setup lang="ts">
import { useLanguageStore } from '#/store/language.ts'
const languageStore = useLanguageStore()
</script>
<template>
<select
v-model="languageStore.language">
<option
v-for="language in languageStore.languages"
:key="language"
:value="language"
>
{{ language }}
</option>
</select>
</template>
The code works as expected, but in the Vue devtools inspector languageStore.language, languageStore.languages, and language.state are undefined. Why would that be?
screen shot of Vue devtools inspector
I found a away to solve it, though I think its not perfect
// should use computed
const languages = computed(() => languageStore.languages )

Vuejs script setup cannot contain ES module exports

Following the setup guide for Vuejs and Pinia
<script setup>
import {useStore} from "../stores/store.js";
export default {
setup() {
const store = useStore();
return {
store,
}
},
}
</script>
I get the following error from Vite:
[vite] Internal server error: [#vue/compiler-sfc] <script setup> cannot contain ES module exports. If you are using a previous version of <script setup>, please consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.
How do I move to a version of <script setup> that will allow me to do the above?
Thanks!
A bit of confusion on my end it seems. The docs talk about adding <script setup> and then also demonstrate using setup(){}, but they don't explicitly state that its one or the other.
Method 1:
<script setup>
import {useStore} from "../stores/store.js";
const store = useStore();
// do stuff
</script>
Method 2:
<script>
import { defineComponent } from 'vue'
import {useStore} from "../stores/store.js";
export default defineComponent({
setup() {
const store = useStore();
// do stuff
return {
store,
}
}
})
</script>
I think you mismatched two methods of local components registration.
Check:
https://vuejs.org/guide/components/registration.html#local-registration
https://vuejs.org/guide/reusability/composables.html#what-is-a-composable
When using SFC with , imported components are automatically registered locally:
<script setup>
import { useStore } from '../store/store.js'
const { store } = useStore()
</script>
Add Following code to your main.js file
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
.use(createPinia())
app.mount('#app')

vue2 and composition api - cannot import store, error [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function

I am using vue 2.6.14 and composition-api 1.3.3 package to use composition api. I have
my main.js like
import Vue from 'vue'
import VueCompositionAPI from '#vue/composition-api'
Vue.use(VueCompositionAPI)
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
I try to setup a store
I have a src folder / store folder / index.js
and inside the index.js
import { reactive } from '#vue/composition-api'
const state = reactive({
counter : 0
})
export default{ state }
inside App.vue I try to import store to use it
<script>
import store from '#/store'
</script>
I get the error Uncaught Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function.
I tried all solutions from here and nothing works. If I remove the import store from '#/store' the error goes away. Using vue 3 s not an option.
How can I solve this?
Thanks
imports are automatically hoisted to the top of the file, so it actually precedes the Vue.use(VueCompositionApi) at runtime.
So these lines:
import Vue from 'vue'
import VueCompositionAPI from '#vue/composition-api'
Vue.use(VueCompositionAPI)
import App from './App.vue' 👈 hoisted
...become:
import Vue from 'vue'
import VueCompositionAPI from '#vue/composition-api'
import App from './App.vue' 👈
Vue.use(VueCompositionAPI)
So the plugin doesn't get installed before App.vue gets imported, leading to the error you observed.
Option 1: Move plugin installation to own file
You can move the installation of #vue/composition-api into its own file that could be imported before App.vue:
// lib/composition-api.js
import Vue from 'vue'
import VueCompositionAPI from '#vue/composition-api'
Vue.use(VueCompositionAPI)
// main.js
import Vue from 'vue'
import './lib/composition-api'
import App from 'App.vue'
demo 1
Option 2: Use require() in App.vue
require the store in the component's setup(), where the #vue/composition-api would've already been installed:
// App.vue
import { defineComponent, computed } from '#vue/composition-api'
export default defineComponent({
setup() {
const store = require('#/store').default
return {
counter: computed(() => store.state.counter),
increment: () => store.state.counter++,
}
},
})
demo 2
Option 3: Use import() in App.vue
Dynamically import the store with import(). This is especially needed in Vite, which does not have require().
// App.vue
import { defineComponent, computed, ref } from '#vue/composition-api'
export default defineComponent({
setup() {
const store = ref()
import('#/store').then(mod => store.value = mod.default)
return {
counter: computed(() => store.value?.state.counter),
increment: () => store.value && store.value.state.counter++,
}
},
})
demo 3

How to test this component?

I have a Page level component which implements a component BookingInformation with slots. In the Page component, it's got another component BookingInformationHeader with slots. header and default.
My question is, how should I set up my test so that I can test that the GoogleConversionTrackingImage is visible when #Reservation.State wasBookingJustMade changes to true?
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Reservation } from "#/store/vuex-decorators";
import { BookingInformation, BookingInformationHeader } from "#/components";
import GoogleConversionTrackingImage from './components/GoogleConversionTrackingImage.vue';
#Component({
components: {
BookingInformation,
BookingInformationHeader,
GoogleConversionTrackingImage
}
})
export default class ConfirmationPage extends Vue {
renderTrackingImage: boolean = false;
#Reservation.State wasBookingJustMade: boolean;
}
</script>
<template>
<booking-information page-type="confirmation-page" class="confirmation-page">
<template slot="header" slot-scope="bookingInfo">
<booking-information-header>
<template slot="buttons">
// some buttons
</template>
</booking-information-header>
<google-conversion-tracking-image v-if="wasBookingJustMade" />
</template>
</booking-information>
</template>
By using vue test utils https://vue-test-utils.vuejs.org/ and chai https://www.chaijs.com/ in your test file you can do something like:
import mount from "#vue/test-utils";
import expect from "chai";
const wrapper = mount(BookingInformation,<inner components you want to test>);
expect(googleImage.exists()).to.be.true;
wrapper.setData({
wasBookingJustMade: true,
});
const googleImage = wrapper.find("google-conversion-tracking-image");
expect(googleImage.exists()).to.be.false;
You'll probably need to import the page level component as well.
You can give an id to the component you want to find and then search by id.

Load store into vue js

while I am trying to load page it's showing me error "TypeError: Cannot read property 'state' of undefined". As per documentation I have tried this bus not loading store.
<template>
<div>
</div>
</template>
<script>
import { store } from '#/components/tenant/store/store'
import Vue from 'vue'
export default {
store:store,
data: () => {
return {
}
}
}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const userStore = new Vuex.Store({
state: {}
})
Maybe I have misunderstood your question, but since you're exporting the const userStore, don't you need to do the following:
import { userStore } from '#/components/tenant/store/store';