From pinia-v3
Guides Pinia v3 setup for Vue 3 state management with defineStore, getters, actions, option/setup syntaxes, Nuxt SSR, Vuex migration, hydration, and testing.
npx claudepluginhub secondsky/claude-skills --plugin pinia-v3This skill uses the workspace's default tool permissions.
**Status**: Production Ready ✅
assets/example-template.txtreferences/example-reference.mdreferences/plugins-composables.mdreferences/ssr-and-nuxt.mdreferences/state-getters-actions.mdreferences/store-syntax-guide.mdreferences/testing-guide.mdreferences/vuex-migration-checklist.mdreferences/vuex-migration.mdscripts/api-store-example.tsscripts/auth-store-example.tsscripts/example-script.shscripts/option-store-template.tsscripts/persistence-plugin.tsscripts/setup-store-template.tsApplies Acme Corporation brand guidelines including colors, fonts, layouts, and messaging to generated PowerPoint, Excel, and PDF documents.
Builds DCF models with sensitivity analysis, Monte Carlo simulations, and scenario planning for investment valuation and risk assessment.
Calculates profitability (ROE, margins), liquidity (current ratio), leverage, efficiency, and valuation (P/E, EV/EBITDA) ratios from financial statements in CSV, JSON, text, or Excel for investment analysis.
Status: Production Ready ✅ Last Updated: 2025-11-11 Dependencies: Vue 3 (or Vue 2.7 with @vue/composition-api) Latest Versions: pinia@^3.0.4, @pinia/nuxt@^0.11.2, @pinia/testing@^1.0.2
bun add pinia
# or
bun add pinia
# or
bun add pinia
For Vue <2.7 users: Also install @vue/composition-api with bun add @vue/composition-api
Why this matters:
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
CRITICAL:
app.use(pinia) before mounting the app// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>
Load references/store-syntax-guide.md for complete comparison of Option vs Setup stores.
Pinia supports two store definition syntaxes:
Option Stores:
$reset() methodSetup Stores:
→ Load references/store-syntax-guide.md for: Complete syntax comparison, examples, choosing criteria
Load references/state-getters-actions.md for complete API reference.
State:
state: () => ({...}) (option) or ref() (setup)store.countstore.count++ or store.$patch({...})store.$reset() (option stores only)Getters:
getters: { double: (state) => state.count * 2 }this (must type return value)Actions:
actions: { increment() { this.count++ } }Store Destructuring:
import { storeToRefs } from 'pinia'
// ✅ For reactivity
const { name, count } = storeToRefs(store)
// ✅ Actions can destructure directly
const { increment } = store
→ Load references/state-getters-actions.md for: Complete API, subscriptions, store composition patterns, Options API usage
Load references/plugins-composables.md for complete plugin and composables guide.
pinia.use(({ store, options }) => {
// Add properties to every store
return { customProperty: 'value' }
})
Option Stores: Limited to useLocalStorage style in state()
Setup Stores: Full VueUse/composables support
→ Load references/plugins-composables.md for: Complete plugin patterns, VueUse integration, TypeScript typing, common patterns (persistence, router, logger)
Stores need the Pinia instance, which is auto-injected in components but not available in module scope.
// router.ts
import { useUserStore } from '@/stores/user'
// ❌ Fails: Pinia not installed yet
const userStore = useUserStore()
router.beforeEach((to) => {
if (userStore.isLoggedIn) { /* ... */ }
})
// router.ts
import { useUserStore } from '@/stores/user'
router.beforeEach((to) => {
// ✅ Works: Called after Pinia is installed
const userStore = useUserStore()
if (userStore.isLoggedIn) { /* ... */ }
})
Why it works: Router guards execute AFTER app.use(pinia) completes.
// server-side
export function setupRouter(pinia) {
router.beforeEach((to) => {
const userStore = useUserStore(pinia) // Pass explicitly
})
}
Load references/ssr-and-nuxt.md for complete SSR and Nuxt integration guide.
State Hydration:
devalue() (not JSON.stringify)useStore()useStore() BEFORE await in actionsbunx nuxi@latest module add pinia
Auto-imports: defineStore, storeToRefs, usePinia, acceptHMRUpdate, all stores
→ Load references/ssr-and-nuxt.md for: Complete SSR patterns, Nuxt configuration, server-side data fetching, SSR pitfalls, debugging
Load references/testing-guide.md for complete testing guide.
import { setActivePinia, createPinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia()) // Fresh Pinia for each test
})
bun add -d @pinia/testing
import { createTestingPinia } from '@pinia/testing'
mount(Component, {
global: { plugins: [createTestingPinia()] }
})
→ Load references/testing-guide.md for: Complete test patterns, stubbing actions, mocking getters, async testing, SSR testing
// stores/counter.ts
import { defineStore, acceptHMRUpdate } from 'pinia'
export const useCounterStore = defineStore('counter', {
// store definition
})
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
}
if (import.meta.webpackHot) {
import.meta.webpackHot.accept(acceptHMRUpdate(useCounterStore, import.meta.webpackHot))
}
Benefits:
For projects still using Options API, load complete mapper documentation.
→ Load references/state-getters-actions.md for: Complete Options API integration, all mappers (mapStores, mapState, mapWritableState, mapActions)
Load references/vuex-migration.md for complete migration guide.
Key Changes:
namespaced (automatic via store ID)mutations (direct state mutation)commit() with direct mutationsrootState/rootGetters with store importsstore.$reset() instead of custom clear mutationsDirectory: store/modules/ → stores/ (each module = separate store)
→ Load references/vuex-migration.md for: Complete conversion steps, component migration, checklist, gradual migration strategy
✅ Define all state properties in state() or return them from setup stores
✅ Use storeToRefs() when destructuring state/getters in components
✅ Call app.use(pinia) BEFORE mounting the app
✅ Return all state from setup stores (private state breaks SSR/DevTools)
✅ Call useStore() inside functions/callbacks when used outside components
✅ Use acceptHMRUpdate() for development HMR support
✅ Type return values when getters use this to access other getters
✅ Use devalue for SSR state serialization (prevents XSS)
✅ Hydrate state BEFORE calling any useStore() on the client (SSR)
✅ Call all useStore() BEFORE any await in async actions (SSR)
❌ Add state properties dynamically after store creation
❌ Destructure store directly without storeToRefs() (loses reactivity)
❌ Use arrow functions for actions (need this context)
❌ Return private state in setup stores (breaks SSR/DevTools/plugins)
❌ Call useStore() at module top-level (before Pinia installed)
❌ Create circular dependencies between stores (both reading each other's state)
❌ Use JSON.stringify() for SSR serialization (vulnerable to XSS)
❌ Call useStore() after await in actions (breaks SSR)
❌ Forget to type getter return values when using this
❌ Skip beforeEach(() => setActivePinia(createPinia())) in unit tests
This skill prevents 12 documented issues:
Error: State changes don't update in template after destructuring
Why It Happens: JavaScript destructuring breaks Vue reactivity
Prevention: Always use storeToRefs() for state/getters
Error: New properties added after store creation aren't reactive
Why It Happens: Pinia needs all properties defined upfront for reactivity
Prevention: Declare all properties in state(), even if initially undefined
Error: getActivePinia() returns undefined
Why It Happens: Calling useStore() before app.use(pinia)
Prevention: Call app.use(pinia) before mounting or accessing stores
Error: State not serialized/hydrated correctly in SSR Why It Happens: Properties not returned from setup aren't tracked Prevention: Return ALL state properties from setup stores
this Don't Infer TypesError: TypeScript can't infer return type when getter uses this
Source: Known TypeScript limitation with Pinia
Prevention: Explicitly type return value: getterName(): ReturnType { ... }
Error: Can't find this.counterStore in component
Why It Happens: mapStores() automatically adds 'Store' suffix
Prevention: Use store name + 'Store' or call setMapStoreSuffix()
await Break SSRError: Wrong Pinia instance used in SSR, causing state pollution
Why It Happens: await changes execution context in async functions
Prevention: Call all useStore() before any await statements
Error: Maximum call stack exceeded Why It Happens: Both stores read each other's state during initialization Prevention: Use getters/actions for cross-store access, not setup-time reads
Error: User input in state can execute malicious scripts
Why It Happens: JSON.stringify() doesn't escape executable code
Prevention: Use devalue library for safe serialization
Error: Changes to store require full page reload
Why It Happens: Vite/webpack HMR not configured for store
Prevention: Add acceptHMRUpdate() block to each store file
Error: Store state contains non-serializable functions
Why It Happens: Option stores state() can only return writable refs
Prevention: Use setup stores for complex composables, or extract only writable state
Error: Tests affect each other, sporadic failures
Why It Happens: Single Pinia instance shared across tests
Prevention: beforeEach(() => setActivePinia(createPinia())) in test suites
Core: pinia@^3.0.4, vue@^3.5.24
Nuxt: @pinia/nuxt@^0.11.2, nuxt@^3.13.0
Testing: @pinia/testing@^1.0.2, vitest@^1.0.0
SSR: devalue@^5.3.2 (for safe serialization)
See reference files for complete pattern examples:
references/state-getters-actions.mdreferences/plugins-composables.mdreferences/store-syntax-guide.md (setup store examples)references/state-getters-actions.md (accessing stores outside components)Solution:
app.use(pinia) is called before mountinguseStore() inside callback/functionuseStore(pinia)Solution: Use storeToRefs() instead of direct destructuring
this has TypeScript errorsSolution: Explicitly type the return value: myGetter(): ReturnType { return this.otherGetter }
Solution: Implement custom reset manually:
function $reset() {
count.value = 0
name.value = ''
}
return { count, name, $reset }
Solution: Add HMR acceptance block:
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useMyStore, import.meta.hot))
}
Solution: Create fresh Pinia in beforeEach():
beforeEach(() => {
setActivePinia(createPinia())
})
Load references/store-syntax-guide.md when:
Load references/state-getters-actions.md when:
$patch, $subscribe, or $onActionmapStores, mapState, mapActions)Load references/plugins-composables.md when:
Load references/ssr-and-nuxt.md when:
Load references/testing-guide.md when:
createTestingPiniaLoad references/vuex-migration.md when:
pinia packagecreatePinia()app.use(pinia) before mountingsrc/stores/)defineStore()storeToRefs() when destructuring in componentsthisacceptHMRUpdate() (development)@pinia/nuxt (if using Nuxt)createTestingPinia() (if testing)use[Name]StoreQuestions? Issues?