All skills
vuejs-ai avatar

/vue-pinia-best-practices

@bc922e4 official
by Vue AIvuejs-ai/skills2.9k stars
167

Pinia stores, state management patterns, store setup, and reactivity with stores.

Use this Skill: https://skilld.dev/gh/vuejs-ai/skills/vue-pinia-best-practices

This session only. Nothing lands on disk.

referencepinia-store-destructuring-breaks-reactivity.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Use storeToRefs When Destructuring Pinia Stores

Impact: HIGH - Pinia stores are wrapped with reactive, so destructuring them directly extracts non-reactive values. Changes to the store won't be reflected in your component, causing stale UI and confusing bugs.

This is one of the most common mistakes when using Pinia, especially for developers coming from Vuex or other state management libraries.

Task Checklist

  • Never destructure state or getters directly from a Pinia store
  • Use storeToRefs() to extract reactive state and getters
  • Destructure actions directly (they don't need reactivity)
  • Remember: storeToRefs is for state/getters, direct destructure is for actions

The Problem: Direct Destructuring

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// WRONG: Direct destructuring breaks reactivity
const { name, email, isLoggedIn } = userStore

// Later, when store updates...
userStore.login({ name: 'John', email: 'john@example.com' })

// name and email are still the OLD values!
// UI won't update because these are no longer reactive
console.log(name) // undefined (or initial value)
</script>

<template>
  <!-- This won't update when store changes -->
  <div>{{ name }}</div>
</template>

The Solution: Use storeToRefs

<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// CORRECT: Use storeToRefs for state and getters
const { name, email, isLoggedIn } = storeToRefs(userStore)

// Actions can be destructured directly (they're just functions)
const { login, logout } = userStore

// Now when the store updates...
login({ name: 'John', email: 'john@example.com' })

// name and email are reactive refs that update automatically
console.log(name.value) // 'John'
</script>

<template>
  <!-- This updates reactively -->
  <div>{{ name }}</div>
  <button @click="logout">Logout</button>
</template>

Understanding Why This Happens

Pinia stores are reactive objects (like reactive()). When you destructure:

const store = useCounterStore()
// store is a reactive Proxy

const { count } = store
// count is now just a primitive number (0), not reactive
// It's like doing: const count = 0

// vs with storeToRefs
const { count } = storeToRefs(store)
// count is now a ref that stays connected to the store
// It's like: const count = computed(() => store.count)

Complete Pattern: State, Getters, and Actions

<script setup>
import { storeToRefs } from 'pinia'
import { useCartStore } from '@/stores/cart'

const cartStore = useCartStore()

// State and getters: USE storeToRefs
const {
  items,           // state
  itemCount,       // getter
  totalPrice,      // getter
  isEmpty          // getter
} = storeToRefs(cartStore)

// Actions: destructure directly
const {
  addItem,
  removeItem,
  clearCart
} = cartStore
</script>

<template>
  <div v-if="isEmpty">Cart is empty</div>
  <div v-else>
    <p>{{ itemCount }} items - ${{ totalPrice }}</p>
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
        <button @click="removeItem(item.id)">Remove</button>
      </li>
    </ul>
    <button @click="clearCart">Clear All</button>
  </div>
</template>

Alternative: Don't Destructure

If you prefer, you can avoid destructuring entirely:

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
// Use userStore.name, userStore.login(), etc. directly
</script>

<template>
  <div>{{ userStore.name }}</div>
  <button @click="userStore.logout()">Logout</button>
</template>

This works fine but is more verbose for stores used frequently in the template.

Common Mistake: Mixing storeToRefs with Actions

<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// WRONG: Don't include actions in storeToRefs
// Actions are just functions and storeToRefs will skip them anyway
const { name, login } = storeToRefs(userStore)
// login is undefined! Actions aren't included in storeToRefs result

// CORRECT: Separate state/getters from actions
const { name } = storeToRefs(userStore)
const { login } = userStore
</script>

TypeScript Tip

With TypeScript, the types work correctly:

import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// name is Ref<string>, email is Ref<string>
const { name, email } = storeToRefs(userStore)

// login is (credentials: Credentials) => Promise<void>
const { login } = userStore

Reference

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides educational content and best practices for Pinia state management in Vue.js applications. It is a documentation-focused skill with no malicious patterns detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    7 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at bc922e4. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 8 months ago
version
1.0.0
author
github.com/vuejs-ai
  • Vue
  • pinia
  • state-management
  • reactivity
  • store-setup
  • devtools
  • ssr

README badge

README badge for vuejs-ai/skills/vue-pinia-best-practices

Provides Pinia store setup patterns, reactivity gotchas, and state management conventions for Vue apps. Covers destructuring pitfalls, DevTools integration, SSR considerations, and filter persistence strategies.

Generated from the current SKILL.md.

Does this skill cover setup stores and options stores?
Yes. The skill addresses both patterns, including common issues like setup stores missing state in DevTools or SSR, and provides guidance on when to use each.
What reactivity issues does this skill handle?
It covers store destructuring breaking reactivity, method binding losing context in templates, and how to maintain reactive updates when accessing store properties.
Does this skill address SSR or DevTools integration?
Yes. It includes troubleshooting for setup stores not exposing state correctly in DevTools and SSR environments.
Does this cover ephemeral state like filters or URL sync?
Yes. The skill includes patterns for handling filters that reset on refresh and guidance on syncing state with URLs for shareable application state.

Generated from the current SKILL.md. These answers refresh after source changes.