feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
389
skills/pinia/references/core-stores.md
Normal file
389
skills/pinia/references/core-stores.md
Normal file
@@ -0,0 +1,389 @@
|
||||
---
|
||||
name: stores
|
||||
description: Defining stores, state, getters, and actions in Pinia
|
||||
---
|
||||
|
||||
# Pinia Stores
|
||||
|
||||
Stores are defined using `defineStore()` with a unique name. Each store has three core concepts: **state**, **getters**, and **actions**.
|
||||
|
||||
## Defining Stores
|
||||
|
||||
### Option Stores
|
||||
|
||||
Similar to Vue's Options API:
|
||||
|
||||
```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++
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Think of `state` as `data`, `getters` as `computed`, and `actions` as `methods`.
|
||||
|
||||
### Setup Stores (Recommended)
|
||||
|
||||
Uses Composition API syntax - more flexible and powerful:
|
||||
|
||||
```ts
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const name = ref('Eduardo')
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, name, doubleCount, increment }
|
||||
})
|
||||
```
|
||||
|
||||
In Setup Stores: `ref()` → state, `computed()` → getters, `function()` → actions.
|
||||
|
||||
**Important:** You must return all state properties for Pinia to track them.
|
||||
|
||||
### Using Stores
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
|
||||
const store = useCounterStore()
|
||||
// Access: store.count, store.doubleCount, store.increment()
|
||||
</script>
|
||||
```
|
||||
|
||||
### Destructuring with storeToRefs
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
|
||||
const store = useCounterStore()
|
||||
|
||||
// ❌ Breaks reactivity
|
||||
const { name, doubleCount } = store
|
||||
|
||||
// ✅ Preserves reactivity for state/getters
|
||||
const { name, doubleCount } = storeToRefs(store)
|
||||
|
||||
// ✅ Actions can be destructured directly
|
||||
const { increment } = store
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State
|
||||
|
||||
State is defined as a function returning the initial state.
|
||||
|
||||
### TypeScript
|
||||
|
||||
Type inference works automatically. For complex types:
|
||||
|
||||
```ts
|
||||
interface UserInfo {
|
||||
name: string
|
||||
age: number
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
userList: [] as UserInfo[],
|
||||
user: null as UserInfo | null,
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
Or use an interface for the return type:
|
||||
|
||||
```ts
|
||||
interface State {
|
||||
userList: UserInfo[]
|
||||
user: UserInfo | null
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: (): State => ({
|
||||
userList: [],
|
||||
user: null,
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
### Accessing and Modifying
|
||||
|
||||
```ts
|
||||
const store = useStore()
|
||||
store.count++
|
||||
```
|
||||
|
||||
```vue
|
||||
<input v-model="store.count" type="number" />
|
||||
```
|
||||
|
||||
### Mutating with $patch
|
||||
|
||||
Apply multiple changes at once:
|
||||
|
||||
```ts
|
||||
// Object syntax
|
||||
store.$patch({
|
||||
count: store.count + 1,
|
||||
name: 'DIO',
|
||||
})
|
||||
|
||||
// Function syntax (for complex mutations)
|
||||
store.$patch((state) => {
|
||||
state.items.push({ name: 'shoes', quantity: 1 })
|
||||
state.hasChanged = true
|
||||
})
|
||||
```
|
||||
|
||||
### Resetting State
|
||||
|
||||
Option Stores have built-in `$reset()`. For Setup Stores, implement your own:
|
||||
|
||||
```ts
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
|
||||
function $reset() {
|
||||
count.value = 0
|
||||
}
|
||||
|
||||
return { count, $reset }
|
||||
})
|
||||
```
|
||||
|
||||
### Subscribing to State Changes
|
||||
|
||||
```ts
|
||||
cartStore.$subscribe((mutation, state) => {
|
||||
mutation.type // 'direct' | 'patch object' | 'patch function'
|
||||
mutation.storeId // 'cart'
|
||||
mutation.payload // patch object (only for 'patch object')
|
||||
|
||||
localStorage.setItem('cart', JSON.stringify(state))
|
||||
})
|
||||
|
||||
// Options
|
||||
cartStore.$subscribe(callback, { flush: 'sync' }) // Immediate
|
||||
cartStore.$subscribe(callback, { detached: true }) // Keep after unmount
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getters
|
||||
|
||||
Getters are computed values, equivalent to Vue's `computed()`.
|
||||
|
||||
### Basic Getters
|
||||
|
||||
```ts
|
||||
getters: {
|
||||
doubleCount: (state) => state.count * 2,
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing Other Getters
|
||||
|
||||
Use `this` with explicit return type:
|
||||
|
||||
```ts
|
||||
getters: {
|
||||
doubleCount: (state) => state.count * 2,
|
||||
doublePlusOne(): number {
|
||||
return this.doubleCount + 1
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Getters with Arguments
|
||||
|
||||
Return a function (note: loses caching):
|
||||
|
||||
```ts
|
||||
getters: {
|
||||
getUserById: (state) => {
|
||||
return (userId: string) => state.users.find((user) => user.id === userId)
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Cache within parameterized getters:
|
||||
|
||||
```ts
|
||||
getters: {
|
||||
getActiveUserById(state) {
|
||||
const activeUsers = state.users.filter((user) => user.active)
|
||||
return (userId: string) => activeUsers.find((user) => user.id === userId)
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Accessing Other Stores in Getters
|
||||
|
||||
```ts
|
||||
import { useOtherStore } from './other-store'
|
||||
|
||||
getters: {
|
||||
combined(state) {
|
||||
const otherStore = useOtherStore()
|
||||
return state.localData + otherStore.data
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Actions
|
||||
|
||||
Actions are methods for business logic. Unlike getters, they can be asynchronous.
|
||||
|
||||
### Defining Actions
|
||||
|
||||
```ts
|
||||
actions: {
|
||||
increment() {
|
||||
this.count++
|
||||
},
|
||||
randomizeCounter() {
|
||||
this.count = Math.round(100 * Math.random())
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Async Actions
|
||||
|
||||
```ts
|
||||
actions: {
|
||||
async registerUser(login: string, password: string) {
|
||||
try {
|
||||
this.userData = await api.post({ login, password })
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Accessing Other Stores in Actions
|
||||
|
||||
```ts
|
||||
import { useAuthStore } from './auth-store'
|
||||
|
||||
actions: {
|
||||
async fetchUserPreferences() {
|
||||
const auth = useAuthStore()
|
||||
if (auth.isAuthenticated) {
|
||||
this.preferences = await fetchPreferences()
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**SSR:** Call all `useStore()` before any `await`:
|
||||
|
||||
```ts
|
||||
async orderCart() {
|
||||
// ✅ Call stores before await
|
||||
const user = useUserStore()
|
||||
|
||||
await apiOrderCart(user.token, this.items)
|
||||
// ❌ Don't call useStore() after await in SSR
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribing to Actions
|
||||
|
||||
```ts
|
||||
const unsubscribe = someStore.$onAction(
|
||||
({ name, store, args, after, onError }) => {
|
||||
const startTime = Date.now()
|
||||
console.log(`Start "${name}" with params [${args.join(', ')}]`)
|
||||
|
||||
after((result) => {
|
||||
console.log(`Finished "${name}" after ${Date.now() - startTime}ms`)
|
||||
})
|
||||
|
||||
onError((error) => {
|
||||
console.warn(`Failed "${name}": ${error}`)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
unsubscribe() // Cleanup
|
||||
```
|
||||
|
||||
Keep subscription after component unmount:
|
||||
|
||||
```ts
|
||||
someStore.$onAction(callback, true)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Options API Helpers
|
||||
|
||||
```ts
|
||||
import { mapState, mapWritableState, mapActions } from 'pinia'
|
||||
import { useCounterStore } from '../stores/counter'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
// Readonly state/getters
|
||||
...mapState(useCounterStore, ['count', 'doubleCount']),
|
||||
// Writable state
|
||||
...mapWritableState(useCounterStore, ['count']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCounterStore, ['increment']),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessing Global Providers in Setup Stores
|
||||
|
||||
```ts
|
||||
import { inject } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSearchFilters = defineStore('search-filters', () => {
|
||||
const route = useRoute()
|
||||
const appProvided = inject('appProvided')
|
||||
|
||||
// Don't return these - access them directly in components
|
||||
return { /* ... */ }
|
||||
})
|
||||
```
|
||||
|
||||
<!--
|
||||
Source references:
|
||||
- https://pinia.vuejs.org/core-concepts/
|
||||
- https://pinia.vuejs.org/core-concepts/state.html
|
||||
- https://pinia.vuejs.org/core-concepts/getters.html
|
||||
- https://pinia.vuejs.org/core-concepts/actions.html
|
||||
-->
|
||||
Reference in New Issue
Block a user