Skill v1.0.1
currentLLM-judged scan100/100+17 new, ~1 modified
version: "1.0.1" name: pinia-v3 description: "Pinia v3 Vue state management with defineStore, getters, actions. Use for Vue 3 stores, Nuxt SSR, Vuex migration, or encountering store composition, hydration, testing errors."
metadata: keywords:
- pinia
- vue state management
- pinia stores
- defineStore
- vue 3 state
- state management
- getters
- actions
- pinia plugins
- pinia ssr
- nuxt pinia
- vuex migration
- store composition
- pinia testing
- setup stores
- option stores
- storeToRefs
- mapState
- mapActions
- state hydration
- pinia nuxt module
- createPinia
- useStore
- pinia devtools
- pinia hmr
- hot module replacement
license: MIT
Pinia v3 - Vue State Management
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
Quick Start (5 Minutes)
1. Install Pinia
bun add pinia# orbun add pinia# orbun add pinia
For Vue <2.7 users: Also install @vue/composition-api with bun add @vue/composition-api
Why this matters:
- Pinia is the official Vue state management library
- Provides better TypeScript support than Vuex
- Eliminates mutations and namespacing complexity
- Full DevTools support with time-travel debugging
2. Create and Register Pinia Instance
// main.tsimport { 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:
- Install Pinia BEFORE using any store
- Call
app.use(pinia)before mounting the app - Only one Pinia instance per application (unless SSR)
3. Define Your First Store
// stores/counter.tsimport { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0,name: 'Eduardo'}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}})
4. Use Store in Components
<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>
The Two Store Syntaxes
Load `references/store-syntax-guide.md` for complete comparison of Option vs Setup stores.
Quick Overview
Pinia supports two store definition syntaxes:
Option Stores:
- Similar to Vue Options API
- Built-in
$reset()method - Best for: Simpler use cases, teams familiar with Vuex
Setup Stores:
- Uses Composition API pattern
- Full composables integration
- Best for: Advanced patterns, need watchers/VueUse integration
→ Load `references/store-syntax-guide.md` for: Complete syntax comparison, examples, choosing criteria
State, Getters, and Actions
Load `references/state-getters-actions.md` for complete API reference.
Quick Reference
State:
- Define in
state: () => ({...})(option) orref()(setup) - Access directly:
store.count - Mutate directly:
store.count++orstore.$patch({...}) - Reset:
store.$reset()(option stores only)
Getters:
- Computed properties:
getters: { double: (state) => state.count * 2 } - Access other getters with
this(must type return value)
Actions:
- Business logic:
actions: { increment() { this.count++ } } - Can be async
- Access other stores directly
Store Destructuring:
import { storeToRefs } from 'pinia'// ✅ For reactivityconst { name, count } = storeToRefs(store)// ✅ Actions can destructure directlyconst { increment } = store
→ Load `references/state-getters-actions.md` for: Complete API, subscriptions, store composition patterns, Options API usage
Plugins and Composables
Load `references/plugins-composables.md` for complete plugin and composables guide.
Plugin Basics
pinia.use(({ store, options }) => {// Add properties to every storereturn { customProperty: 'value' }})
Composables Integration
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)
Using Stores Outside Components
The Problem
Stores need the Pinia instance, which is auto-injected in components but not available in module scope.
❌ Wrong: Accessing Store at Module Level
// router.tsimport { useUserStore } from '@/stores/user'// ❌ Fails: Pinia not installed yetconst userStore = useUserStore()router.beforeEach((to) => {if (userStore.isLoggedIn) { /* ... */ }})
✅ Right: Accessing Store Inside Callbacks
// router.tsimport { useUserStore } from '@/stores/user'router.beforeEach((to) => {// ✅ Works: Called after Pinia is installedconst userStore = useUserStore()if (userStore.isLoggedIn) { /* ... */ }})
Why it works: Router guards execute AFTER app.use(pinia) completes.
SSR: Explicit Pinia Instance
// server-sideexport function setupRouter(pinia) {router.beforeEach((to) => {const userStore = useUserStore(pinia) // Pass explicitly})}
Server-Side Rendering & Nuxt
Load `references/ssr-and-nuxt.md` for complete SSR and Nuxt integration guide.
SSR Quick Reference
State Hydration:
- Server: Serialize with
devalue()(notJSON.stringify) - Client: Hydrate BEFORE calling
useStore() - Critical: Call all
useStore()BEFOREawaitin actions
Nuxt 3/4 Integration
bunx 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
Testing
Load `references/testing-guide.md` for complete testing guide.
Testing Quick Start
import { setActivePinia, createPinia } from 'pinia'beforeEach(() => {setActivePinia(createPinia()) // Fresh Pinia for each test})
Component Testing
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
Hot Module Replacement (HMR)
Vite Setup
// stores/counter.tsimport { defineStore, acceptHMRUpdate } from 'pinia'export const useCounterStore = defineStore('counter', {// store definition})if (import.meta.hot) {import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))}
Webpack Setup
if (import.meta.webpackHot) {import.meta.webpackHot.accept(acceptHMRUpdate(useCounterStore, import.meta.webpackHot))}
Benefits:
- Edit stores without full page reload
- Preserve application state during development
- Faster development iteration
Options API Usage
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)
Migrating from Vuex
Load `references/vuex-migration.md` for complete migration guide.
Quick Conversion Overview
Key Changes:
- Remove
namespaced(automatic via store ID) - Eliminate
mutations(direct state mutation) - Replace
commit()with direct mutations - Replace
rootState/rootGetterswith store imports - Use
store.$reset()instead of custom clear mutations
Directory: store/modules/ → stores/ (each module = separate store)
→ Load `references/vuex-migration.md` for: Complete conversion steps, component migration, checklist, gradual migration strategy
Critical Rules
Always Do
✅ 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)
Never Do
❌ 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
Known Issues Prevention
This skill prevents 12 documented issues:
Issue #1: Lost Reactivity from Direct Destructuring
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
Issue #2: Cannot Add State Properties Dynamically
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
Issue #3: Store Not Found Before Pinia Install
Error: getActivePinia() returns undefined Why It Happens: Calling useStore() before app.use(pinia) Prevention: Call app.use(pinia) before mounting or accessing stores
Issue #4: Setup Store Private State Breaks SSR
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
Issue #5: Getters with this Don't Infer Types
Error: TypeScript can't infer return type when getter uses this Source: Known TypeScript limitation with Pinia Prevention: Explicitly type return value: getterName(): ReturnType { ... }
Issue #6: Options API Store Suffix Confusion
Error: Can't find this.counterStore in component Why It Happens: mapStores() automatically adds 'Store' suffix Prevention: Use store name + 'Store' or call setMapStoreSuffix()
Issue #7: Actions Called After await Break SSR
Error: 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
Issue #8: Circular Store Dependencies Crash App
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
Issue #9: XSS Vulnerability in SSR State Serialization
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
Issue #10: HMR Doesn't Work in Development
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
Issue #11: Composables Return Functions Break Option Stores
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
Issue #12: State Not Reset Between Unit Tests
Error: Tests affect each other, sporadic failures Why It Happens: Single Pinia instance shared across tests Prevention: beforeEach(() => setActivePinia(createPinia())) in test suites
Package Versions (Verified 2025-11-21)
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)
Common Patterns
See reference files for complete pattern examples:
- Authentication stores →
references/state-getters-actions.md - Persistence plugins →
references/plugins-composables.md - Form stores →
references/store-syntax-guide.md(setup store examples) - Router integration →
references/state-getters-actions.md(accessing stores outside components)
Official Documentation
- Pinia: https://pinia.vuejs.org/
- Getting Started: https://pinia.vuejs.org/getting-started.html
- Core Concepts: https://pinia.vuejs.org/core-concepts/
- SSR Guide: https://pinia.vuejs.org/ssr/
- Nuxt Integration: https://pinia.vuejs.org/ssr/nuxt.html
- Testing: https://pinia.vuejs.org/cookbook/testing.html
- Vuex Migration: https://pinia.vuejs.org/cookbook/migration-vuex.html
- GitHub: https://github.com/vuejs/pinia
Troubleshooting
Problem: "getActivePinia() was called with no active Pinia"
Solution:
- Ensure
app.use(pinia)is called before mounting - If outside component, call
useStore()inside callback/function - For SSR, pass pinia instance explicitly:
useStore(pinia)
Problem: State changes don't update in template
Solution: Use storeToRefs() instead of direct destructuring
Problem: Getter using this has TypeScript errors
Solution: Explicitly type the return value: myGetter(): ReturnType { return this.otherGetter }
Problem: $reset() not available in setup store
Solution: Implement custom reset manually:
function $reset() {count.value = 0name.value = ''}return { count, name, $reset }
Problem: HMR not working for stores
Solution: Add HMR acceptance block:
if (import.meta.hot) {import.meta.hot.accept(acceptHMRUpdate(useMyStore, import.meta.hot))}
Problem: Tests fail intermittently
Solution: Create fresh Pinia in beforeEach():
beforeEach(() => {setActivePinia(createPinia())})
When to Load References
Load `references/store-syntax-guide.md` when:
- Need detailed comparison between Option and Setup store syntaxes
- Deciding which syntax to use for a new store
- Questions about Option vs Setup stores trade-offs
- Need complete examples of both syntaxes
Load `references/state-getters-actions.md` when:
- Need complete API reference for state, getters, or actions
- Questions about
$patch,$subscribe, or$onAction - Implementing store composition patterns
- Using Options API mappers (
mapStores,mapState,mapActions) - Accessing stores outside components (router, plugins)
Load `references/plugins-composables.md` when:
- Creating custom Pinia plugins
- Integrating VueUse or other composables into stores
- Need persistence, routing, or logging plugin patterns
- Questions about TypeScript typing for plugins
- Advanced composables integration
Load `references/ssr-and-nuxt.md` when:
- Setting up server-side rendering
- Integrating with Nuxt 3/4
- Questions about state hydration or serialization
- SSR-related errors (wrong Pinia instance, hydration mismatch)
- Nuxt auto-imports or configuration
- Server-side data fetching patterns
Load `references/testing-guide.md` when:
- Setting up unit tests for stores
- Testing components that use Pinia stores
- Need to stub actions or mock getters
- Questions about
createTestingPinia - Testing SSR stores
- Vitest or testing framework integration
Load `references/vuex-migration.md` when:
- Migrating existing Vuex codebase to Pinia
- Questions about Vuex→Pinia conversion
- Need migration checklist or examples
- Gradual migration strategy needed
Complete Setup Checklist
- [ ] Installed
piniapackage - [ ] Created Pinia instance with
createPinia() - [ ] Registered with
app.use(pinia)before mounting - [ ] Created stores directory (e.g.,
src/stores/) - [ ] Defined at least one store with
defineStore() - [ ] Used
storeToRefs()when destructuring in components - [ ] Typed getter return values when using
this - [ ] Added HMR support with
acceptHMRUpdate()(development) - [ ] Configured SSR hydration (if using SSR)
- [ ] Configured
@pinia/nuxt(if using Nuxt) - [ ] Set up testing with
createTestingPinia()(if testing) - [ ] All stores follow consistent naming:
use[Name]Store - [ ] Verified DevTools integration works
Questions? Issues?
- Check official docs: https://pinia.vuejs.org/
- Review "Known Issues Prevention" section above
- Verify setup checklist is complete
- Check for TypeScript configuration issues
- Ensure Pinia is installed before using stores