Pinia Actions and Getters
A Pinia store is more than a bag of reactive state — actions and getters give it behavior and derived data. Actions are methods defined on the store that can contain arbitrary logic, including asynchronous calls, and they are the recommended place to mutate state (though direct mutation is also allowed, unlike Vuex). Getters, on the other hand, are the store's equivalent of computed properties: they take the store's state (and other getters) as input and produce a derived value that is cached until a dependency changes. Together they let you keep components thin — components read getters and call actions, while the store owns the logic and the data-fetching side effects.
Cricket analogy: A team manager (the store) doesn't just hold the scoresheet — the coach calls plays like "declare an innings" (actions) while the scoreboard operator computes run-rate (getters) automatically from raw scores, keeping the captain free to focus on tactics.
Defining Actions
Actions are declared in the actions option of defineStore's options API, or simply as regular functions when using the setup-style store. Inside an action you have full access to this, which refers to the store instance, so you can read and write any state property, call other actions, or invoke getters. Actions can be asynchronous — you can await a fetch call and assign the result directly to state, without needing separate 'commit' and 'dispatch' calls as Vuex required. This makes Pinia actions feel much closer to writing a plain class method than to firing a Redux-style action creator.
Cricket analogy: A team's action "callForDRS()" is just a method on the captain (this refers to the captain himself), who can directly check the replay and update the scoreboard without filing a formal appeal to the umpire's committee first, unlike older review systems.
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
isCheckingOut: false,
}),
getters: {
itemCount: (state) => state.items.reduce((sum, i) => sum + i.qty, 0),
totalPrice(state) {
// getters can call other getters via `this`
return state.items.reduce((sum, i) => sum + i.price * i.qty, 0)
},
isEmpty: (state) => state.items.length === 0,
},
actions: {
addItem(product, qty = 1) {
const existing = this.items.find((i) => i.id === product.id)
if (existing) {
existing.qty += qty
} else {
this.items.push({ ...product, qty })
}
},
async checkout(paymentApi) {
this.isCheckingOut = true
try {
await paymentApi.charge(this.totalPrice)
this.items = []
} finally {
this.isCheckingOut = false
}
},
},
})Getters as Cached Computed Values
Getters behave exactly like Vue's computed(): they are lazily evaluated and cached based on their reactive dependencies, so accessing cartStore.totalPrice twice without any state change does not re-run the calculation. Getters can accept arguments by returning a function from the getter, but in that case the result is no longer cached per call — only the outer function reference is cached, so use this pattern sparingly for genuinely parameterized lookups such as getItemById(id).
Cricket analogy: A "current run rate" getter behaves like a scoreboard computed once per over — checking it twice between deliveries gives the same cached number, but a "runsScoredBy(playerId)" parameterized version recalculates every single call since only the lookup function itself is cached.
In the Composition API 'setup store' syntax, there is no separate getters or actions block — you write a computed() for what would be a getter and a plain function for what would be an action, all inside the store's setup function. Both styles produce an identical store shape and can be mixed across a codebase, though teams typically pick one for consistency.
A common pitfall is destructuring a store in a component with plain object destructuring, e.g. const { itemCount, addItem } = useCartStore(). This strips reactivity from getters and state (though actions remain callable since they're just functions). Always use storeToRefs(store) to extract reactive state and getters while keeping their reactive connection to the store.
Calling Actions From Other Actions
Because actions are just methods on the store instance, one action can call another via this.otherAction(), and it can also call actions from a completely different store by importing that store's use...Store() composable and invoking it inside the action body. This cross-store composition is straightforward compared to Vuex's module namespacing and is one of the reasons Pinia stores tend to stay small and focused — a checkout action can freely call into a useCartStore and a useUserStore in the same function.
Cricket analogy: A captain's "declareInnings()" action can call his own "signalUmpire()" action directly, and also reach into the opposing captain's "acceptDeclaration()" via a separate team object, letting a "matchResult()" routine freely coordinate both teams' state without a central federation namespace.
- Actions are methods on the store that can contain sync or async logic and freely read/write state via
this. - Getters are cached computed values derived from state; they only recompute when a dependency changes.
- Getters that return a function (for parameterized lookups) lose per-call caching.
- Use
storeToRefs(store)when destructuring state and getters to preserve reactivity; actions can be destructured directly. - Actions can call other actions on the same store via
this, or import and call actions on entirely different stores. - Setup-style stores replace
getters/actionsblocks with plaincomputed()calls and functions.
Practice what you learned
1. In a Pinia options-style store, what does `this` refer to inside an action?
2. Why does destructuring `const { itemCount } = useCartStore()` break reactivity?
3. What happens to caching when a getter is written to return a function, e.g. `getItemById: (state) => (id) => state.items.find(i => i.id === id)`?
4. Which statement about actions calling other stores is correct?
5. In a setup-style Pinia store, how do you define the equivalent of a 'getter'?
Was this page helpful?
You May Also Like
Pinia Basics
Get started with Pinia, Vue's official state management library, covering stores, state, and how components read and mutate shared data.
Sharing State Across Components
Compare the tools Vue offers for cross-component state sharing — props/emits, provide/inject, composables, and Pinia — and when to reach for each.
Computed Properties
How Vue's computed() function derives cached, reactive values from other state, and why it's preferred over methods for derived data in templates.
Fetching Data in Vue
Learn common patterns for fetching data in Vue components and composables, including lifecycle timing, reactivity pitfalls, and race conditions.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics