vuejs / pinia

🍍 Intuitive, type safe, light and flexible Store for Vue using the composition api with DevTools support
https://pinia.vuejs.org
MIT License
13.11k stars 1.05k forks source link

Document how to keep internal action calls trackable #2483

Closed bodograumann closed 12 months ago

bodograumann commented 12 months ago

What problem is this solving

When defining stores with the setup syntax, it quickly happens that one action calls another. Similarly you are allowed to setup a watcher inside a store, which again triggers an action.

These action calls trigger the plain function directly and pinia cannot notice it. Thus you cannot observe them with $onAction and they are not visible in the devtools.

Proposed solution

I am working around the issue by calling the action as if it was on another store:

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  const self = useCounterStore()
  function add(n: number) {
    for (const i = 0; i < n; i++) {
      self.increment()
    }
  }

  watch(count, (currentCount) => {
    if (currentCount > 10) {
      self.square()
    }
  })

  return { count, doubleCount, increment, square }
})

Most importantly this is something that should be documented as the recommended way to call actions from other actions even within the same store, so users won't run into the issues described above.

Is it possible to add self as a parameter to the setup function even? I am a bit uncertain whether type-inferrence works there. E.g.:

export const useCounterStore = defineStore('counter', (self) => {
  …
})

Describe alternatives you've considered

I looked through all kinds of issues and discussions to see whether this issue has been discussed before, but suprisingly couldn't find any mention of it.

posva commented 12 months ago

This is a design limitation… making it possible requires a verbose wrapping of the actions:

defineStore('store', ({ action, state, shallowState }) => {
  const myAction = action(() => {})
  // etc
  return { myAction }
})

that’s why it wasn’t added. Within the store, actions are just functions after all I wouldn’t do the nested call of the store so I cannot recommend it in the docs

bodograumann commented 12 months ago

I wouldn’t do the nested call of the store so I cannot recommend it in the docs

It also wouldn't be my first choice, but I don't see any alternative. Arguably you could also forgo seeing these action calls in the devtools / handling them with $onAction. Shouldn't the user at least be made aware of this limitation and given the option to use such a nested call?

Are there any major downsides to doing the nested call? It is after all just how actions in different stores already call each other.

posva commented 12 months ago

I think it might be worth leading a proper RFC in the discussions in this repo. There are many factors to take into consideration like type safety, possible runtime errors and error prone patterns for nested store calls. All for the sake of $onAction. In other words: it might not be worth If it’s for dev tools, I believe creating build plugins could be the key

Preisy commented 4 months ago

Hello!

I have encountered a similar issue in my project, where we have numerous internal action calls, particularly for prefetching data during store initialization. This became problematic when I attempted to emit an event for every store action to handle updates.

Adding this feature would greatly improve the Pinia user experience!