--- 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: ```ts 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: ```ts 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 ```vue ``` ### Destructuring with storeToRefs ```vue ``` --- ## State State is defined as a function returning the initial state. ### TypeScript Type inference works automatically. For complex types: ```ts 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: ```ts interface State { userList: UserInfo[] user: UserInfo | null } export const useUserStore = defineStore('user', { state: (): State => ({ userList: [], user: null, }), }) ``` ### Accessing and Modifying ```ts const store = useStore() store.count++ ``` ```vue ``` ### Mutating with $patch Apply multiple changes at once: ```ts // 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: ```ts export const useCounterStore = defineStore('counter', () => { const count = ref(0) function $reset() { count.value = 0 } return { count, $reset } }) ``` ### Subscribing to State Changes ```ts 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 ``` --- ## Getters Getters are computed values, equivalent to Vue's `computed()`. ### Basic Getters ```ts getters: { doubleCount: (state) => state.count * 2, } ``` ### Accessing Other Getters Use `this` with explicit return type: ```ts getters: { doubleCount: (state) => state.count * 2, doublePlusOne(): number { return this.doubleCount + 1 }, }, ``` ### Getters with Arguments Return a function (note: loses caching): ```ts getters: { getUserById: (state) => { return (userId: string) => state.users.find((user) => user.id === userId) }, }, ``` Cache within parameterized getters: ```ts 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 ```ts 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 ```ts actions: { increment() { this.count++ }, randomizeCounter() { this.count = Math.round(100 * Math.random()) }, }, ``` ### Async Actions ```ts actions: { async registerUser(login: string, password: string) { try { this.userData = await api.post({ login, password }) } catch (error) { return error } }, }, ``` ### Accessing Other Stores in Actions ```ts 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`: ```ts 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 ```ts 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: ```ts someStore.$onAction(callback, true) ``` --- ## Options API Helpers ```ts 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 ```ts 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 { /* ... */ } }) ```