Martin
Martin

Reputation: 81

Can a Vue component/plugin have its own pinia state, so that multiple component instances don't share the same state

I have a "standalone" component which is set up as a Vue plugin (to be downloaded via npm and used in projects) and it uses pinia, but it looks like multiple instances of the component share the same pinia state. Is there a way to set up pinia such that each component instance has its own state?

The component is made up of multiple sub-(sub)-components and I'm using pinia to manage its overall state. Imagine something fairly complex like a <fancy-calendar /> component but you could have multiple calendars on a page.

I have the standard pinia set up in an index.js:

import myPlugin from "./myPlugin.vue";
import { createPinia } from "pinia";

const pinia = createPinia();

export function myFancyPlugin(app, options) {
    app.use(pinia);
    app.component("myPlugin", myPlugin);
}

Then myPlugin.vue has:

<script setup>
import { useMyStore } from '@/myPlugin/stores/myStore'
import { SubComponent1 } from '@/myPlugin/components/SubComponent1'
import { SubComponent2 } from '@/myPlugin/components/SubComponent2'
...

const store = useMyStore()

The sub-components also import the store. Also some of the sub-components also have their own sub-components which also use the store.

myStore.js is set up like this:

import { defineStore } from "pinia";

export const useMyStore = defineStore("myStore", {
    state: () => ({
        ...
    }),

    getters: {
        ...
    },

    actions: {
        ... 
    }
});

Edit: This is the solution I ended up using:

myStore.js:

import { defineStore } from "pinia"

export const useMyStore = (id) =>
    defineStore(id, {
        state: () => ({
            ...
        }),

        getters: {},

        actions: {},
    })();

myPlugin.vue

...
<script setup>
import { provide } from "vue"
import { useMyStore } from '@/MyNewPlugin/stores/MyStore'
import { v4 } from "uuid"

const storeId = v4()
provide('storeId', storeId)
const store = useMyStore(storeId)
...

SubComponent1.vue

<script setup>
import { inject } from "vue"
import { useMyStore } from '@/MyNewPlugin/stores/MyStore'

const storeId = inject('storeId')
const store = useMyStore(storeId)
</script>

Upvotes: 4

Views: 2200

Answers (1)

tao
tao

Reputation: 90237

A simple way of solving this is to create a stores map, using unique identifiers:

  1. When you init a new instance of the root component of your plugin, you create a unique identifier for the current instance:
import { v4 } from 'uuid'

const storeId = v4();
  1. You pass this id to its descendants via props or provide/inject.
  2. Whenever a descendent component calls the store, it calls it with the storeId:
const store = useMyStore(storeId)
  1. Finally, inside myStore:
const storesMap = {};

export const useMyStore = (id) => {
  if (!storesMap[id]) {
    storesMap[id] = defineStore(id, {
      state: () => ({ ... }),
      actions: {},
      getters: {}
    })
  }

  return storesMap[id]()
}

Haven't tested it, but I don't see why it wouldn't work.


If you need hands-on help, you'll have to provide a runnable minimal reproducible example on which I could test implementing the above.

Upvotes: 3

Related Questions