| name | pinia-stores |
| description | Pinia store patterns using Setup Store syntax with TypeScript. Use when creating stores, managing global state, or implementing actions and getters. |
Pinia Stores Skill
Patterns for creating Pinia stores using the Setup Store syntax.
When to Use This Skill
- Creating new Pinia stores
- Managing global application state
- Implementing async actions with API calls
- Creating computed getters
- Sharing state between components
Reference Documentation
For detailed patterns and conventions, see:
Quick Reference
Setup Store Structure
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import type { Task } from '@/types/task'
export const useTasksStore = defineStore('tasks', () => {
const tasks = ref<Task[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
const completedTasks = computed(() =>
tasks.value.filter(t => t.status.name === 'Done')
)
const taskCount = computed(() => tasks.value.length)
async function fetchTasks() {
isLoading.value = true
error.value = null
try {
const response = await fetch('/api/tasks')
tasks.value = await response.json()
} catch (e) {
error.value = 'Failed to fetch tasks'
} finally {
isLoading.value = false
}
}
async function createTask(data: Partial<Task>) {
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
const newTask = await response.json()
tasks.value.push(newTask)
return newTask
}
function $reset() {
tasks.value = []
isLoading.value = false
error.value = null
}
return {
tasks,
isLoading,
error,
completedTasks,
taskCount,
fetchTasks,
createTask,
$reset
}
})
Critical Rules
- Use Setup Store syntax (function style, not options)
- Never mutate state directly from components
- Always implement
$reset for testing
- Handle loading and error states in async actions
- Type all state with TypeScript
Using Store in Components
<script setup lang="ts">
import { useTasksStore } from '@/stores/tasks'
import { storeToRefs } from 'pinia'
const tasksStore = useTasksStore()
// Destructure with storeToRefs for reactivity
const { tasks, isLoading, error } = storeToRefs(tasksStore)
// Actions can be destructured directly
const { fetchTasks, createTask } = tasksStore
// Fetch on mount
onMounted(() => {
fetchTasks()
})
</script>
Filtering Pattern
const filterStatus = ref<string | null>(null)
const filteredTasks = computed(() => {
if (!filterStatus.value) return tasks.value
return tasks.value.filter(t => t.status.name === filterStatus.value)
})
function setFilter(status: string | null) {
filterStatus.value = status
}
Optimistic Updates
async function updateTask(id: string, data: Partial<Task>) {
const index = tasks.value.findIndex(t => t.id === id)
const original = { ...tasks.value[index] }
tasks.value[index] = { ...original, ...data }
try {
await fetch(`/api/tasks/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch {
tasks.value[index] = original
throw error
}
}
File Naming
- Stores:
featureName.ts (e.g., tasks.ts)
- Location:
frontend/src/stores/
- Tests:
featureName.spec.ts alongside