← Back to Business Systems Specialist Collection

Skill · Version 1.0.0

Pinia shared state

Places Vue state at the right scope and uses Pinia only for information genuinely shared across views.

pinia-state

Team members using this skill

See how it helps

When is it useful?

Use when stores grow unnecessarily or shared values stop updating correctly.

What does it help you do?

  • Distinguishes component, URL, subtree and application-level state.
  • Defines store state, computed getters and actions with predictable reset behavior.
  • Preserves reactivity when consuming stores and respects serialization needs.

Try asking this

Refactor our workspace store so shared selection persists across routes while form drafts remain local and reset on dismissal. Switch routes with a selected workspace, then close and reopen an edited form. Shared selection should persist while the discarded draft does not reappear.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Pinia & Shared State

Introduce a global store only when state genuinely outlives a single view or is shared across independent branches of the component tree.

1. Choosing the right state scope

  • Component-local ref / computed: state used by one component or a parent and its direct children (open modals, form drafts, local toggles).
  • URL query / route params: filters, search terms, sorting, pagination and active tabs that should survive page reloads and be shareable by link.
  • Scoped provide / inject: state shared within a subtree or compound component family that should reset when that subtree unmounts.
  • Pinia store: authenticated user/session context, cross-route workspaces, shopping carts, notification queues, or client caches shared across unrelated routes. Never move every form field or modal toggle into Pinia just for convenience.

2. Defining Setup Stores cleanly

  • Prefer Setup Stores (defineStore('id', () => { ... })) for TypeScript inference and composable integration, unless the codebase standardizes on Option Stores:
    • ref() / shallowRef() become state properties.
    • computed() becomes getters.
    • function (sync or async) becomes actions.
  • Return every state ref from the setup store function! Private unreturned refs break SSR serialization, Vue DevTools inspection and store.$state plugins.
  • In Setup Stores, store.$reset() is not built in by default — implement an explicit function $reset() action that restores initial values when reset behavior is needed.

3. Consuming and composing stores safely

  • Never destructure state or getters directly from a store instance (const { count } = useCounterStore() breaks reactivity). Always wrap state and getters in storeToRefs(store), and destructure actions directly from store:
    const store = useCartStore()
    const { items, total } = storeToRefs(store)
    const { addItem, clear } = store
    
  • Call useXStore() inside <script setup>, setup(), actions or router beforeEach guards — never at module top level before app.use(pinia) has run (especially in SSR or unit tests).
  • When composing stores, call the other store inside the setup store body or inside an action, and never create circular store dependencies where Store A and Store B read each other's state synchronously during initialization.
  • Use store.$patch({ ... }) or store.$patch((state) => { ... }) when applying multiple related state mutations together.

Before you finish

  • Confirmed the state belongs in Pinia rather than local refs, URL params or scoped provide/inject.
  • Setup store returns all state refs, computed getters and actions, with an explicit reset action if needed.
  • Consumers use storeToRefs(store) for state/getters and never instantiate stores at module import time.
  • No circular synchronous dependencies between stores; unit tests initialize a fresh Pinia instance per test.

Included files 2

  • LICENSE.txt
  • SKILL.md