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
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 )
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')
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
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.
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';