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
// frontend/src/stores/tasks.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import type { Task } from '@/types/task'
export const useTasksStore = defineStore('tasks', () => {
// State
const tasks = ref<Task[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
// Getters (computed)
const completedTasks = computed(() =>
tasks.value.filter(t => t.status.name === 'Done')
)
const taskCount = computed(() => tasks.value.length)
// Actions
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 public API
return {
// State
tasks,
isLoading,
error,
// Getters
completedTasks,
taskCount,
// Actions
fetchTasks,
createTask,
$reset
}
})
Critical Rules
- Use Setup Store syntax (function style, not options)
- Never mutate state directly from components
- Always implement
$resetfor 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
// In store
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>) {
// Find and update optimistically
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 {
// Rollback on error
tasks.value[index] = original
throw error
}
}
File Naming
- Stores:
featureName.ts(e.g.,tasks.ts) - Location:
frontend/src/stores/ - Tests:
featureName.spec.tsalongside
