brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
vue-piniaVue 3 state management with Pinia using the Composition API, covering setup stores, SSR/Nuxt integration, persistence, and testing. Use when creating or refactoring Pinia stores, deciding what state belongs in a store versus a component, wiring stores into router guards, persisting store state to storage, or testing store actions with @pinia/testing.
Independent GitHub snapshot · Automated classification · Original documentation governs
Pinia is the standard state management library for Vue 3, and setup stores built with the Composition API give shared application state the same ergonomics as local ref/computed state.
stores/, e.g. stores/cart.ts, using defineStore('cart', () => { ... }).ref/reactive, derive values with computed, and put all writes/side effects in functions (actions).<script setup>, destructure reactive state with storeToRefs(), and destructure actions directly.@pinia/testing.ref, reactive, or computed.useFetch / useAsyncData, TanStack Query for Vue, or the existing API layer for server state — Pinia is for client state, not a data-fetching cache.defineStore('name', () => { ... }) for Vue 3 Composition API projects over the older options-API defineStore({ state, getters, actions }) form.stores/, such as stores/cart.ts or stores/session.ts.ref is invisible to the rest of the system.// stores/cart.ts
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
interface CartLine {
id: string
name: string
quantity: number
unitPrice: number
}
export const useCartStore = defineStore('cart', () => {
const lines = ref<CartLine[]>([])
const itemCount = computed(() =>
lines.value.reduce((total, line) => total + line.quantity, 0),
)
const subtotal = computed(() =>
lines.value.reduce((total, line) => total + line.quantity * line.unitPrice, 0),
)
function addLine(line: CartLine) {
const existing = lines.value.find((item) => item.id === line.id)
if (existing) {
existing.quantity += line.quantity
return
}
lines.value.push(line)
}
function removeLine(id: string) {
lines.value = lines.value.filter((line) => line.id !== id)
}
function clearCart() {
lines.value = []
}
return {
lines,
itemCount,
subtotal,
addLine,
removeLine,
clearCart,
}
})
<script setup> or inside setup functions, getters, and actions — never conditionally, and never inside a plain .js/.ts module loaded at import time.storeToRefs() when destructuring store state or getters in components, so reactivity is preserved.storeToRefs().watch that just recomputes a value should usually be a computed.<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
const { itemCount, subtotal } = storeToRefs(cart)
const { clearCart } = cart
</script>
<template>
<div>
<p>{{ itemCount }} items — ${{ subtotal.toFixed(2) }}</p>
<button type="button" :disabled="itemCount === 0" @click="clearCart">
Clear cart
</button>
</div>
</template>
any; use unknown and narrow external inputs (API responses, persisted storage) before committing them to state.{ status: 'idle' } | { status: 'loading' } | { status: 'error'; error: string }.defineStore) stable and descriptive because they appear in devtools and persistence keys.status, error, and lastUpdatedAt fields when the UI depends on them.connect()/disconnect() action pair).localStorage, window) during server rendering — guard with import.meta.client (Nuxt) or an onMounted check.@pinia/nuxt integration and SSR-safe composables for data fetching instead of manually wiring a Pinia instance.// router guard example (outside setup)
router.beforeEach((to) => {
const auth = useAuthStore(pinia) // pass the active Pinia instance explicitly
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return { name: 'login' }
}
})
localStorage directly.@pinia/testing (createTestingPinia()) for component tests that need stores, with actions stubbed by default.acceptHMRUpdate() in stores when the project uses Vite HMR patterns.import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach } from 'vitest'
import { useCartStore } from '@/stores/cart'
describe('cart store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('merges quantities for duplicate line items', () => {
const cart = useCartStore()
cart.addLine({ id: '1', name: 'Widget', quantity: 1, unitPrice: 10 })
cart.addLine({ id: '1', name: 'Widget', quantity: 2, unitPrice: 10 })
expect(cart.itemCount).toBe(3)
})
})
storeToRefs(); this breaks reactivity silently.