pinia/references/core-stores.md
Version e53a142a2420.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
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:
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:
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
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// Access: store.count, store.doubleCount, store.increment()
</script>
Destructuring with storeToRefs
<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:
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:
interface State {
userList: UserInfo[]
user: UserInfo | null
}
export const useUserStore = defineStore('user', {
state: (): State => ({
userList: [],
user: null,
}),
})
Accessing and Modifying
const store = useStore()
store.count++
<input v-model="store.count" type="number" />
Mutating with $patch
Apply multiple changes at once:
// 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:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
Subscribing to State Changes
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
flush: 'sync' caveats: it runs the callback after every state change instead of batching, so it can hurt performance if the callback is heavy or state changes often. It also matters for correctness in one niche case: a direct mutation (e.g. store.count++) happening synchronously right after a $patch() in the same tick won't trigger a non-sync subscription on its own — use flush: 'sync' if you must be notified of every such mutation.
Getters
Getters are computed values, equivalent to Vue's computed().
Basic Getters
getters: {
doubleCount: (state) => state.count * 2,
}
Accessing Other Getters
Use this with explicit return type:
getters: {
doubleCount: (state) => state.count * 2,
doublePlusOne(): number {
return this.doubleCount + 1
},
},
Getters with Arguments
Return a function (note: loses caching):
getters: {
getUserById: (state) => {
return (userId: string) => state.users.find((user) => user.id === userId)
},
},
Cache within parameterized getters:
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
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
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
Async Actions
actions: {
async registerUser(login: string, password: string) {
try {
this.userData = await api.post({ login, password })
} catch (error) {
return error
}
},
},
Accessing Other Stores in Actions
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:
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
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:
someStore.$onAction(callback, true)
Options API Helpers
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
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 { /* ... */ }
})