Agent Skills: Pinia v3 - Vue State Management

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.

UncategorizedID: secondsky/claude-skills/pinia-v3

Install this agent skill to your local

pnpm dlx add-skill https://github.com/secondsky/claude-skills/tree/HEAD/plugins/pinia-v3/skills/pinia-v3

Skill Files

Browse the full folder contents for pinia-v3.

Download Skill

Loading file tree…

plugins/pinia-v3/skills/pinia-v3/SKILL.md

Skill Metadata

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."

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
# 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:

  • 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.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:

  • 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.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++
    }
  }
})

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) or ref() (setup)
  • Access directly: store.count
  • Mutate directly: store.count++ or store.$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 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


Plugins and Composables

Load references/plugins-composables.md for complete plugin and composables guide.

Plugin Basics

pinia.use(({ store, options }) => {
  // Add properties to every store
  return { 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.ts
import { useUserStore } from '@/stores/user'

// ❌ Fails: Pinia not installed yet
const userStore = useUserStore()

router.beforeEach((to) => {
  if (userStore.isLoggedIn) { /* ... */ }
})

✅ Right: Accessing Store Inside Callbacks

// 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.

SSR: Explicit Pinia Instance

// server-side
export 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() (not JSON.stringify)
  • Client: Hydrate BEFORE calling useStore()
  • Critical: Call all useStore() BEFORE await in 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.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))
}

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:

  1. Remove namespaced (automatic via store ID)
  2. Eliminate mutations (direct state mutation)
  3. Replace commit() with direct mutations
  4. Replace rootState/rootGetters with store imports
  5. 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 storesreferences/state-getters-actions.md
  • Persistence pluginsreferences/plugins-composables.md
  • Form storesreferences/store-syntax-guide.md (setup store examples)
  • Router integrationreferences/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:

  1. Ensure app.use(pinia) is called before mounting
  2. If outside component, call useStore() inside callback/function
  3. 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 = 0
  name.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 pinia package
  • [ ] 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?

  1. Check official docs: https://pinia.vuejs.org/
  2. Review "Known Issues Prevention" section above
  3. Verify setup checklist is complete
  4. Check for TypeScript configuration issues
  5. Ensure Pinia is installed before using stores