pinia

import { defineStore } from 'pinia';

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pinia" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-pinia

Pinia Core Knowledge

Store Definition

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, name: 'Eduardo', }),

getters: { doubleCount: (state) => state.count * 2, doublePlusOne(): number { return this.doubleCount + 1; }, },

actions: { increment() { this.count++; }, async fetchData() { const data = await api.getData(); this.count = data.count; }, }, });

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: pinia for comprehensive documentation.

Setup Syntax (Composition API)

export const useCounterStore = defineStore('counter', () => { const count = ref(0); const name = ref('Eduardo');

const doubleCount = computed(() => count.value * 2);

function increment() { count.value++; }

async function fetchData() { const data = await api.getData(); count.value = data.count; }

return { count, name, doubleCount, increment, fetchData }; });

Usage in Components

<script setup> import { useCounterStore } from '@/stores/counter'; import { storeToRefs } from 'pinia';

const store = useCounterStore();

// Reactive destructure const { count, doubleCount } = storeToRefs(store);

// Actions can be destructured directly const { increment } = store; </script>

<template> <button @click="increment">{{ count }}</button> <p>Double: {{ doubleCount }}</p> </template>

Persist Plugin

import { createPinia } from 'pinia'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia(); pinia.use(piniaPluginPersistedstate);

// In store export const useUserStore = defineStore('user', { state: () => ({ token: '' }), persist: true, // or { storage: sessionStorage } });

When NOT to Use This Skill

Scenario Use Instead

React applications zustand or redux-toolkit

Server state (API data, caching) Vue composables with useFetch or useAsyncData

Component-local state Vue's ref /reactive

Vuex legacy projects Migrate to Pinia first, or keep Vuex for now

Simple key-value storage localStorage or sessionStorage directly

Anti-Patterns

Anti-Pattern Why It's Bad Correct Approach

Using Options API stores Less flexible than Composition API Use defineStore with setup function

Not using storeToRefs

Loses reactivity on destructuring Wrap with storeToRefs(store)

Storing server data in Pinia No cache invalidation Use composables with fetch/axios

Persisting everything Large storage, security risks Only persist necessary state

Mutating state from components Breaks single source of truth Always use actions

Circular dependencies between stores Hard to debug, initialization issues Use getters or separate composables

Not resetting stores on logout Data leaks between users Call $reset() or reset manually

Using global Pinia instance everywhere Hard to test Pass pinia instance explicitly in tests

No TypeScript types Loses type safety Define interfaces for state/getters/actions

Accessing stores outside setup Can cause reactivity issues Only use stores in setup or composables

Quick Troubleshooting

Issue Cause Solution

Lost reactivity after destructuring Not using storeToRefs

Use const { count } = storeToRefs(store)

"getActivePinia was called with no active Pinia" Store used before app mount or outside Vue Ensure app.use(pinia) before accessing stores

Persist not working Plugin not installed Add pinia.use(piniaPluginPersistedstate)

State not resetting with $reset()

Using setup syntax without reset logic Manually implement reset or use Options API

TypeScript errors with getters Wrong return type inference Explicitly type getter return value

Actions not updating components State not reactive Use ref() or reactive() in setup stores

Hot reload breaks stores HMR issues with Vite Add if (import.meta.hot) { acceptHMRUpdate(...) }

Can't access router in store Router not injected Inject router via plugin or pass as argument

Production Readiness

Store Organization

// stores/index.ts - Centralized store setup import { createPinia } from 'pinia'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import { markRaw } from 'vue'; import router from '@/router';

export const pinia = createPinia();

// Add plugins pinia.use(piniaPluginPersistedstate);

// Add router to all stores pinia.use(({ store }) => { store.router = markRaw(router); });

// stores/authStore.ts - Production-ready auth store export const useAuthStore = defineStore('auth', () => { const user = ref<User | null>(null); const token = ref<string | null>(null); const isAuthenticated = computed(() => !!token.value);

async function login(credentials: LoginCredentials) { try { const response = await api.login(credentials); token.value = response.token; user.value = response.user; return { success: true }; } catch (error) { return { success: false, error: error.message }; } }

function logout() { token.value = null; user.value = null; // Clear all other stores const userStore = useUserStore(); userStore.$reset(); }

return { user, token, isAuthenticated, login, logout }; }, { persist: { key: 'auth', storage: localStorage, paths: ['token'], // Only persist token }, });

Security Best Practices

// Secure persistence with encryption import CryptoJS from 'crypto-js'; import type { StorageLike } from 'pinia-plugin-persistedstate';

const SECRET = import.meta.env.VITE_STORE_SECRET;

const encryptedStorage: StorageLike = { getItem(key: string): string | null { const encrypted = localStorage.getItem(key); if (!encrypted) return null; try { const bytes = CryptoJS.AES.decrypt(encrypted, SECRET); return bytes.toString(CryptoJS.enc.Utf8); } catch { return null; } }, setItem(key: string, value: string): void { const encrypted = CryptoJS.AES.encrypt(value, SECRET).toString(); localStorage.setItem(key, encrypted); }, };

export const useSecureStore = defineStore('secure', { state: () => ({ sensitiveData: null }), persist: { storage: encryptedStorage, }, });

Testing Stores

// tests/stores/authStore.test.ts import { setActivePinia, createPinia } from 'pinia'; import { useAuthStore } from '@/stores/authStore'; import { vi } from 'vitest';

describe('AuthStore', () => { beforeEach(() => { setActivePinia(createPinia()); });

it('should login successfully', async () => { const store = useAuthStore(); vi.spyOn(api, 'login').mockResolvedValue({ token: 'jwt-token', user: { id: '1', name: 'John' }, });

const result = await store.login({ email: 'test@example.com', password: 'password' });

expect(result.success).toBe(true);
expect(store.isAuthenticated).toBe(true);
expect(store.user?.name).toBe('John');

});

it('should clear state on logout', () => { const store = useAuthStore(); store.token = 'token'; store.user = { id: '1', name: 'John' };

store.logout();

expect(store.token).toBeNull();
expect(store.user).toBeNull();
expect(store.isAuthenticated).toBe(false);

}); });

Error Handling

// stores/errorStore.ts export const useErrorStore = defineStore('error', () => { const errors = ref<AppError[]>([]);

function addError(error: AppError) { errors.value.push({ ...error, id: crypto.randomUUID(), timestamp: Date.now(), });

// Auto-remove after 5 seconds
setTimeout(() => {
  removeError(error.id);
}, 5000);

}

function removeError(id: string) { errors.value = errors.value.filter((e) => e.id !== id); }

return { errors, addError, removeError }; });

// Usage with composable export function useApi<T>(fn: () => Promise<T>) { const errorStore = useErrorStore(); const loading = ref(false); const data = ref<T | null>(null);

async function execute() { loading.value = true; try { data.value = await fn(); } catch (error) { errorStore.addError({ message: error.message, type: 'error' }); } finally { loading.value = false; } }

return { data, loading, execute }; }

Monitoring Metrics

Metric Target

Store hydration time < 50ms

Action execution time < 100ms

Memory footprint Minimal

Test coverage

90%

Checklist

  • Composition API stores (setup syntax)

  • storeToRefs for reactive destructuring

  • Persist plugin for auth state

  • Encrypted storage for sensitive data

  • $reset() for clearing state

  • Router accessible in stores

  • Comprehensive store tests

  • Error handling with error store

  • DevTools integration

  • No circular dependencies between stores

Reference Documentation

  • Composition Stores

  • Plugins

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Web3

cryptography

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

langchain

No summary provided by upstream source.

Repository SourceNeeds Review
Web3

supply-chain

No summary provided by upstream source.

Repository SourceNeeds Review