In Pinia, how does an option store differ from a setup store, and how does defineStore map a setup store's refs, computeds and functions?
answer
- second argument decides the kind
- state, getters, actions object
- ref, computed, function mapping
- watchers and composables inside
- option stores run through setup
basics
~20 sAn option store passes an object of state(), getters and actions; a setup store passes a function whose returned refs become state, computeds getters and functions actions. Setup stores allow watchers and almost any composable; only option stores get $reset().
solid answer
~50 s`defineStore(id, second)` checks whether the second argument is a function. An **option store** passes `{ state, getters, actions }`: `state` is a function returning initial data, getters are derived values, actions use `this`. A **setup store** passes a function written like `<script setup>`: returned `ref()`s and `reactive()`s become state, `computed()`s become getters and functions become actions. Internally Pinia turns an option store into a setup function, so both share devtools, plugins and HMR. The differences are what you can write inside: a setup store can call `watch()`, `inject()` and almost any composable, while an option store can only call a composable inside `state()` and keep the writable refs it returns; in exchange a setup store must return all its state and has no built-in `$reset()`. The docs say to pick the style your team is comfortable with.
code
ts · 13 linesimport { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({ items: [] as { sku: string; qty: number }[] }),
getters: {
itemCount: (state) => state.items.reduce((n, i) => n + i.qty, 0),
},
actions: {
addItem(sku: string) {
this.items.push({ sku, qty: 1 })
},
},
})go deeper
Recall the two call shapes and the setup mapping: ref is state, computed is a getter, a function is an action. Mention that the returned object is what counts.
Explain the trade-offs concretely: watchers, composables and inject in setup stores, $reset and this-based code in option stores, and that the id is always the first argument in Pinia 4.
Show you know an option store is compiled to a setup function internally, so tooling behaves alike, and that the real setup-store risk is state you forgot to return.
Treat the choice as a codebase convention: agree on one default style, and allow the other only where a store genuinely needs watchers, composables or free resets.
## One function, two shapes `defineStore()` in Pinia 4 has exactly two call shapes, and the type of the second argument decides which kind of store you get: - `defineStore('cart', { state, getters, actions })` creates an **option store**; - `defineStore('cart', () => { …; return { … } }, options?)` creates a **setup store**, with an optional third argument for custom options that plugins read. The older `defineStore({ id: 'cart', … })` form, with the id inside the object, was removed in Pinia 3. Both kinds produce the same thing: a `useCartStore()` function whose first call builds a reactive store object registered under its id. ## Option stores An option store mirrors a component written with the Options API: - `state` is a **function** returning the initial state, the store's `data`; - `getters` are derived values, the store's `computed`; - `actions` are methods that read and write state through `this`, the store's `methods`. Internally Pinia converts these options into a setup function: the object returned by `state()` is placed in the pinia state tree and exposed as refs, each getter becomes a `computed`, and the actions are copied in. That generated function then goes through the same code path a setup store uses. This is why both kinds behave alike in devtools, in plugins and during hot module replacement. ## Setup stores A setup store is written like `<script setup>` or a composable. Pinia inspects the object you **return** and classifies each value: | Returned value | Becomes | |---|---| | `ref()` that is not a computed, or `reactive()` | state | | `computed()` | a getter | | a function | an action | Because the body is ordinary Composition API code, it commonly holds: - `watch()` calls that react to the store's own state; - composables, including ones that expose functions or readonly data, whose writable refs you return as state and whose functions you return as actions; - `inject()` or `useRoute()` for app-level provided values, used inside the store but not returned, since Pinia runs the body in the app's injection context; - calls to other stores' `use…Store()` functions. An option store can use a composable too, but only inside `state()`, and only one that returns writable refs; composables that expose functions or readonly data need a setup store. The price of the setup form is that you must return every piece of state yourself: a ref you do not return is invisible to Pinia. ## Side by side | Aspect | Option store | Setup store | |---|---|---| | Shape | object of `state`, `getters`, `actions` | function returning refs, computeds and functions | | State inside actions | `this.items` | `items.value` | | Composables | only inside `state()`, and only ones returning writable refs | almost any, in the body | | Watchers in the definition | no place for them in the options | `watch()` in the body | | `$reset()` | built in | throws in development, does nothing in production | | TypeScript | getters that use `this` need explicit return types | inferred from ordinary code | | Typical slip | little to forget | not returning a piece of state | `$reset()` works for option stores because Pinia can call `state()` again; a setup store has no such function, so you write a reset action yourself. ## Choosing between them 1. **Team style first.** The Pinia docs say to pick the syntax you are most comfortable with; both are first-class and both are current. 2. **Pick a setup store** when the store needs `watch()`, a composable such as a debounced search, or an injected value. 3. **Pick an option store** when the store is plain data plus a few actions and you want `$reset()` for free. 4. **Stay consistent** inside one codebase, so readers do not switch mental models from file to file. A cart store makes a fair test case. As an option store it is short and resettable. As a setup store it can `watch()` the item count to refresh a shipping quote, but it must return `items` and the quote, and its reset is hand-written. Interviewers mostly want the mapping rule stated precisely, plus one honest trade-off in each direction rather than a verdict that one style is simply better.
- What does Pinia do internally with an option store?It converts the options into a setup function: the `state()` result goes into the pinia state tree and is exposed as refs, each getter becomes a `computed`, and the actions are copied in. That function then runs through the same code path as a setup store. So both kinds share devtools, plugin and HMR support; what differs is what you can write inside, plus `$reset()`, which only option stores get.
- Can a Pinia setup store call useRoute() or inject()?Yes. Pinia runs the setup function inside the app's injection context, so `inject()` and `useRoute()` work there as they do in a component. The docs add a warning: do not return those values. The route is a reactive object, so Pinia would file it as store state, and components can call `useRoute()` themselves.
saying these in an interview costs you the question
- Setup stores cannot have getters, only state and actions.
- In a setup store every local ref becomes state, returned or not.
- defineStore({ id: 'cart', state }) is the current way to name a store.
- Option stores are deprecated in favour of setup stores.
- Setup and option stores are different kinds of object at runtime.