Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing pinia root store for component scope #870

Open
bodograumann opened this issue Dec 3, 2021 · 9 comments · May be fixed by #878
Open

Allow changing pinia root store for component scope #870

bodograumann opened this issue Dec 3, 2021 · 9 comments · May be fixed by #878
Labels
✨ enhancement New feature or request

Comments

@bodograumann
Copy link
Contributor

What problem is this solving

When documenting a component with storybook and its docs addon, multiple stories are rendered in one MDX document. That means they are technically part of the same Vue application.
Semantically however, each story represents a very specific state of the component. Usually these components would not access any global state (and thus pinia) at all, but sometimes it is necessary. That means each of the story components needs its own, independant “global” state.

Proposed solution

Pinia should allow to change the root store Pinia instance which is injected into a component and its descendents.
Technically this could be done simply by exposing the piniaSymbol and than I could use provide(piniaSymbol, createPinia()).

On the other hand it might be more prudent to keep the symbol private and expose a convenience function providePinia(pinia) { provide(piniaSymbol, pinia ?? createPinia()) }.

Describe alternatives you've considered

It is also possible to patch alias fields into the storybook webpack config and mock the store definition module which provides the useMyStore function for each store individually. Then each story could set a currentMock property in those modules.
This would be much more complex however, harder to understand and possibly fragile.

@posva
Copy link
Member

posva commented Dec 3, 2021

Probably a providePinia(pinia) (the argument should be mandatory) is the best way so the current app and other properties can be set (like in pinia.install). This should be an easy contribution!

This is like nested pinias

@posva posva linked a pull request Jan 26, 2022 that will close this issue
@posva posva added ✨ enhancement New feature or request and removed feature request labels Jan 27, 2022
@cexoso
Copy link

cexoso commented Jun 1, 2022

I notice the code below
const piniaSymbol = ((process.env.NODE_ENV !== 'production') ? Symbol('pinia') : /* istanbul ignore next */ Symbol());
you can just change Symbol('pinia') to Symbol.for('pinia').
Symbol.for('pinia') allow developer overwrite the Provide by using Vue's provider with the Symbol.for('pinia')
(because Symbol.for('pinia') equal Symbol.for('pinia'))

or export const piniaSymbol

@ropez
Copy link

ropez commented Jun 6, 2022

I'm also looking for something like this, but for a different use-case. I want to explain here, and maybe get some opinions about it.

TL;DR I don't want global state across all pages in the application, but "global state" on each page.

We have a quite large application, with many pages (around 200), all of which are kind of "single page applications" themselves, with data, filters, side bars, popups etc. There is a big navigation menu, and when the user clicks on a different page in the menu, the expected behavior is that the destination page opens with a clean state. If they navigate back to the previous page, it's not expected to maintain the state that it had before (unless it's explicitly set up to do that via some persistence mechanism).

Basically, it's supposed to work as if each menu click re-loads the page. However, we're using vue-router to navigate between the pages without re-loading, because it gives a much more responsive experience.

We're not currently using any state management library, but just a combination of component state, and ad-hoc use of provide/inject on the different pages. I think it would be beneficial to have something like pinia, if we could make each page create a completely independent store, which is disposed when navigating away from the page.

We could perhaps achieve this by calling $reset when opening a page, but it doesn't feel natural. We would easily forget to do it, and create unwanted behavior that would be missed by QA. Also, it seems wasteful to have a big store holding state from all previously visited pages, that we don't need.

I think something like providePinia, would be perfect for our use-case. Alternatively, if the Pinia instance provided a global reset function, that basically destroyed all registered stores, and we could hook that into the router, perhaps that would work.

@johannes-z
Copy link

johannes-z commented Aug 30, 2022

A similar use case is having multiple, complex components in a single page (or nested components -> same app), whereas each component should handle its own pinia instance/store.

@invokermain
Copy link

+1 on this! I think this is a great feature. Simplifying complex nested components with a store is great for keeping things clean. But having the store be a global/singleton means that component is then a bit less reusable/isolated.

Being able to 'scope' your stores to components means the components then are completely self contained/reusable and benefit from the cleaner architecture that using something like pinia provides 😄

Then you could have something neat like (complete pseudocode & probably not the best way to achieve).

<script setup>
...
const props = {
    myId: number
}

const piniaInstance = createPinia()
const componentStore = useComponentStore(piniaInstance)

componentStore.load(myId)
</script>

<template>
    <sub-one :store="componentStore" />
    <sub-two :store="componentStore" />
</template>

Because currently you would have something like this in a sub component:

<script setup>
...
const componentStore = useComponentStore()
</script>

<template>
    ... use the componentStore
</template>

But now the sub component isn't self-documenting or that reusable... it assumes that the store has been loaded with the right data. How do we know what component is in charge of loading the store etc? You can't show two of the component with different data etc.

@ccqgithub
Copy link

When multiple components want to use same store, or a component that with a store will render multiple times, the reusable of store is important.

Now, i can't find a solution in pinia, so i write a tool to reuse store, the repo is here: pinia-di.

@its-lee
Copy link
Contributor

its-lee commented Mar 4, 2023

I'm also looking for something like this, but for a different use-case. I want to explain here, and maybe get some opinions about it.

TL;DR I don't want global state across all pages in the application, but "global state" on each page.

We have a quite large application, with many pages (around 200), all of which are kind of "single page applications" themselves, with data, filters, side bars, popups etc. There is a big navigation menu, and when the user clicks on a different page in the menu, the expected behavior is that the destination page opens with a clean state. If they navigate back to the previous page, it's not expected to maintain the state that it had before (unless it's explicitly set up to do that via some persistence mechanism).

Basically, it's supposed to work as if each menu click re-loads the page. However, we're using vue-router to navigate between the pages without re-loading, because it gives a much more responsive experience.

We're not currently using any state management library, but just a combination of component state, and ad-hoc use of provide/inject on the different pages. I think it would be beneficial to have something like pinia, if we could make each page create a completely independent store, which is disposed when navigating away from the page.

We could perhaps achieve this by calling $reset when opening a page, but it doesn't feel natural. We would easily forget to do it, and create unwanted behavior that would be missed by QA. Also, it seems wasteful to have a big store holding state from all previously visited pages, that we don't need.

I think something like providePinia, would be perfect for our use-case. Alternatively, if the Pinia instance provided a global reset function, that basically destroyed all registered stores, and we could hook that into the router, perhaps that would work.


On this, rather than a pinia-side change; you could have an enforced version of the $reset workflow you're suggesting so that it couldn't be forgotten (I'm aware you're saying this feels unnatural, which is fair):

  • Add a RouteMeta interface in vue-router with a required property called 'store'
  • Update all routes to provide that store's useXXXStore function
  • In a vue-router's before guard, call the route's meta.store property to get the page's store and then call $reset on it.

This automates the process you've described - although relies on you being aware that if you (for some reason) access a different page's store - that any changes made to that store won't be $reset but will be reset by accessing that different page.

@IonianPlayboy
Copy link

I just found out this issue while investigating why the docs generated by Storybook was not playing nicely with some components, and I'm glad there was something to help me understand what was going on.

However, while this issue presents its change as a nice to have, I would argue that it's solving a real problem relevant to end users right now. Currently the Storybook/Pinia integration can create bugs that are hard to understand without the context highlighted here.

Most examples and docs I could find online (like this official tutorial) recommands this way in order to register a Pinia instance for Storybook stories :

import { setup } from '@storybook/vue3';

import { createPinia } from 'pinia';

//👇 Registers a global Pinia instance inside Storybook to be consumed by existing stories
setup((app) => {
   app.use(createPinia());
});

const preview = {
  /* ... */
};

export default preview;

This approach will register a different Pinia instance for each story, and works fine in most cases. I found out the hard way that it creates a bug when some conditions are met:

  • The user render multiple stories for the same component (with autodoc or something equivalent)
  • There is some logic OUTSIDE of every component that will update the store state

If that's the case, then the outside logic that updates the store state won't affect the correct active Pinia instance, it will be dependant on the last instance that was set as active.

Here is a repro with the bug :

This is a simplified repro of my use case : I'm wrapping in a component some third-party code that creates an instance with a distinct id. When this instance emits a specific event, I have a callback that updates the active id inside a store in order to display a modal with the relevant data. This is all happening outside of Vue components.

That's why I would love to see this issue being worked on. In the meantime, I think it would also be nice to add some documentation about the current behavior, but I'm not sure if that would be more appropriate here or on the Storybook repo.

If someone else encounters the same problem, the best way to solve it is to create a single Pinia instance to be shared between all your stories :

import { setup } from '@storybook/vue3';

import { createPinia } from 'pinia';

//👇 Create a single Pinia instance outside of `setup()` to share it between all stories
const pinia = createPinia();
 
setup((app) => {
  app.use(pinia);
});
 

const preview = {
  /* ... */
};

export default preview;

@posva
Copy link
Member

posva commented Oct 28, 2024

@IonianPlayboy The issue you are facing is unrelated to this issue. It's because you are calling the useStore() where it shouldn't. See https://pinia.vuejs.org/core-concepts/outside-component-usage.html#Using-a-store-outside-of-a-component for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ enhancement New feature or request
Projects
Status: 📋 Backlog
Development

Successfully merging a pull request may close this issue.

9 participants