Pinia is Vue's official store, used for anything many unrelated components need: session, cart, settings, caches.
When unrelated components need the same state, a store gives it one home. Pinia is the official choice. Install it with app.use(createPinia()) and define stores with defineStore.
The setup store syntax will feel familiar because it is a composable:
export const useCartStore = defineStore('cart', () => {
const items = ref([]) // state
const total = computed(() => ...) // getter
function add(product) { items.value.push(product) } // action
return { items, total, add }
})Every ref you return becomes state, computeds become getters and functions become actions. You must return all state for devtools and SSR to see it. (An "options store" with state, getters and actions keys also exists.)
In a component, call const cart = useCartStore(). The store is a reactive object, so access cart.items and cart.total without .value. All components share the same instance.
Destructuring a store loses reactivity. Use `storeToRefs(cart)` for state and getters; actions can be destructured directly.
Benefits over a hand-rolled module-level ref: devtools timeline and time travel, SSR safety (one store per request), hot module replacement, $reset/$patch/$subscribe helpers and plugins such as persisted state.
Keep stores focused (one per domain: useAuthStore, useCartStore) and put async calls in actions.
Example
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const total = computed(() => items.value.reduce((s, i) => s + i.price * i.qty, 0))
function add(product) {
const line = items.value.find(i => i.id === product.id)
line ? line.qty++ : items.value.push({ ...product, qty: 1 })
}
async function checkout() {
await fetch('/api/orders', { method: 'POST', body: JSON.stringify(items.value) })
items.value = []
}
return { items, total, add, checkout }
})
// CartBadge.vue <script setup>
import { storeToRefs } from 'pinia'
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
const { items, total } = storeToRefs(cart) // still reactive
const { add } = cart // actions destructure fineRemember
defineStore('id', () => { ... return {...} })defines a setup store- Returned refs = state, computeds = getters, functions = actions; return all state
- Use
storeToRefswhen destructuring state and getters
Watch out for
const { total } = useCartStore()gives a value that never updates; use storeToRefs.