All skills
hyf0 avatar

/vue-pinia-best-practices

@bc922e4 official
by hyf0hyf0/vue-skills2.9k stars
167

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

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

This session only. Nothing lands on disk.

referencepinia-setup-store-return-all-state.md

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

Return All State Properties in Pinia Setup Stores

Impact: HIGH - When using Pinia's setup store syntax (Composition API style), you MUST return all state properties from the setup function. Private state that isn't returned will break Server-Side Rendering (SSR), Vue DevTools inspection, and Pinia plugins.

This is a critical gotcha that can cause silent failures in production.

Task Checklist

  • Return ALL reactive state properties from setup stores
  • Do not create "private" state by omitting it from the return
  • If you need private state, use a prefix convention instead (e.g., _internal)
  • Test stores with DevTools to verify all state is visible
  • Verify SSR hydration includes all necessary state

The Problem: Private State in Setup Stores

// stores/user.js - WRONG: Private state breaks SSR/DevTools
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // Public state
  const name = ref('')
  const email = ref('')

  // "Private" state - NOT returned
  const authToken = ref('')  // Won't be serialized for SSR!
  const lastFetchTime = ref(null)  // Won't appear in DevTools!

  const isLoggedIn = computed(() => !!authToken.value)

  async function login(credentials) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials)
    })
    const data = await response.json()

    authToken.value = data.token  // This state won't transfer to client in SSR!
    name.value = data.name
    email.value = data.email
    lastFetchTime.value = Date.now()
  }

  // WRONG: Not returning authToken and lastFetchTime
  return {
    name,
    email,
    isLoggedIn,
    login
  }
})

What breaks:

  1. SSR Hydration: authToken and lastFetchTime won't be serialized and sent to the client
  2. DevTools: These properties won't appear in the store inspector
  3. Plugins: Persistence plugins won't save these properties
  4. Time-travel debugging: Can't track changes to hidden state

The Solution: Return Everything

// stores/user.js - CORRECT: All state returned
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // All state properties
  const name = ref('')
  const email = ref('')
  const authToken = ref('')
  const lastFetchTime = ref(null)

  // Getters
  const isLoggedIn = computed(() => !!authToken.value)

  // Actions
  async function login(credentials) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials)
    })
    const data = await response.json()

    authToken.value = data.token
    name.value = data.name
    email.value = data.email
    lastFetchTime.value = Date.now()
  }

  function logout() {
    authToken.value = ''
    name.value = ''
    email.value = ''
  }

  // CORRECT: Return ALL state, getters, and actions
  return {
    // State
    name,
    email,
    authToken,
    lastFetchTime,
    // Getters
    isLoggedIn,
    // Actions
    login,
    logout
  }
})

If You Need "Private" State

Use naming conventions instead of actually hiding state:

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // Convention: underscore prefix for "internal" state
  // Still returned, but signals it's not for external use
  const _authToken = ref('')
  const _lastFetchTime = ref(null)

  // Public state
  const name = ref('')
  const email = ref('')

  const isLoggedIn = computed(() => !!_authToken.value)

  // Return everything - convention communicates intent
  return {
    // "Private" - use with caution
    _authToken,
    _lastFetchTime,
    // Public
    name,
    email,
    isLoggedIn
  }
})

Option Stores Don't Have This Problem

With the Options API syntax, all state is automatically tracked:

// stores/user.js - Options syntax: all state is tracked automatically
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    email: '',
    authToken: '',  // Automatically included
    lastFetchTime: null  // Automatically included
  }),

  getters: {
    isLoggedIn: (state) => !!state.authToken
  },

  actions: {
    async login(credentials) {
      // ...
    }
  }
})

How Setup Stores Map to State

Understanding the mapping helps avoid mistakes:

defineStore('example', () => {
  // ref() becomes state
  const count = ref(0)  // → state.count

  // computed() becomes getters
  const double = computed(() => count.value * 2)  // → getters.double

  // Regular functions become actions
  function increment() {  // → actions.increment
    count.value++
  }

  // CRITICAL: Must return all of them
  return { count, double, increment }
})

Debugging: Verify All State is Returned

// Test that DevTools can see all state
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// In DevTools console or tests:
console.log(userStore.$state)
// Should include ALL reactive state properties

// Check what's returned
console.log(Object.keys(userStore))
// Should include: name, email, authToken, lastFetchTime, isLoggedIn, login, logout

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

README badge

README badge for hyf0/vue-skills/vue-pinia-best-practices

Teaches Pinia store setup, reactivity patterns, and state management conventions for Vue applications. Covers common mistakes like destructuring breaking reactivity, setup stores missing state in DevTools, and losing method context in templates.

Generated from the current SKILL.md.

What does this skill cover?
Pinia store setup, state management patterns, reactivity gotchas, and common errors like getActivePinia and broken destructuring in Vue components.
Does this skill address SSR with Pinia?
Yes. It includes guidance on setup stores missing state in DevTools or SSR environments.
How does this handle filter state across page refreshes?
The skill references patterns for persisting ephemeral filters using URL state rather than relying on store alone.
Does this cover store method binding issues?
Yes. It addresses the gotcha where store methods lose context when called directly in templates and explains the parentheses solution.

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