Claude-skills pinia-v3

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.

install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/pinia-v3/skills/pinia-v3" ~/.claude/skills/secondsky-claude-skills-pinia-v3 && rm -rf "$T"
manifest: plugins/pinia-v3/skills/pinia-v3/SKILL.md
source content

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


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