Share global pinia store between components
Asked Answered
J

1

8

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?

Junko answered 10/8, 2022 at 14:23 Comment(0)
G
11

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"

Gelsemium answered 15/9, 2022 at 12:50 Comment(1)
Yes, that is it. Thanks a lot. In the meantime I created my solution by using localStorage with listeners, but in the next project I will use it this way.Junko

© 2022 - 2024 — McMap. All rights reserved.