skip to content

In a Pinia option store, when does a getter use the state argument versus this, and why does a this-based getter need a return type?

level: middleimportance: should knowfreq 48%

answer

  1. arrow for state-only getters
  2. arrow has no own this
  3. this is the whole store
  4. circular type inference
  5. annotate or JSDoc the return

basics

~20 s

Use (state) => … when a getter only reads state; its type is inferred. To read other getters, write a regular function and use this, the whole store. TypeScript cannot infer that circular type, so annotate the return type.

solid answer

~40 s

An arrow getter such as `subtotal: (state) => state.lines.reduce(…)` receives the state as its first argument and gets its return type inferred; the docs pass `state` precisely to encourage arrow functions. An arrow function has no `this` of its own, so combining getters needs a regular function: `total(): number { return this.subtotal + this.tax }`. Pinia calls every getter with the store as `this`, typed as state, getters and plugin properties. The return type is required because inference would be circular: the type of `this` includes `total`, whose type depends on a body that reads `this`, a known TypeScript limitation. In JavaScript, a JSDoc `@returns` gives editors the same type. Arrow getters and regular getters that never touch `this` need no annotation.

code

ts · 20 lines
ts
import { defineStore } from 'pinia'

export const useInvoiceStore = defineStore('invoice', {
  state: () => ({
    lines: [] as { qty: number; unitPrice: number }[],
    taxRate: 0.2,
  }),
  getters: {
    // state only: arrow, type inferred
    subtotal: (state) =>
      state.lines.reduce((sum, l) => sum + l.qty * l.unitPrice, 0),
    // other getters: regular function, return type required
    tax(): number {
      return this.subtotal * this.taxRate
    },
    total(): number {
      return this.subtotal + this.tax
    },
  },
})

go deeper

for a junior

Remember that an arrow getter receives state, and that combining getters needs a regular function using this.

for a middle

Explain why arrows have no store this, why this-based getters need a return type annotation, and what this is typed as.

for a senior

Enforce arrow getters by default, annotated this-based getters where needed, and no actions or writes inside getters.

for a principal

Pick a store convention, option or setup style, partly on how much getter typing ceremony the team is willing to carry.

## Two ways to write an option-store getter Pinia option-store getters come in two shapes, and the choice is about what the getter needs to read. | Shape | Reads | Return type in TypeScript | |---|---|---| | `subtotal: (state) => …` | state only | inferred | | `count(state) { return … }` | state only, regular function | inferred | | `total(): number { return this.subtotal + this.tax }` | other getters, state, the store | must be written | ## The state argument Every getter is called with an argument that is **typed as the store's state**. With an arrow function this is the natural style, and the docs pass `state` first precisely to encourage arrow functions: - the body can only reach state, which keeps the getter simple; - TypeScript infers the return type from the body; - the getter reads well on one line. For an invoice store, `subtotal`, `lineCount` and `hasOverdue` are typical state-only getters. ## Using this As soon as a getter needs **another getter** (`total` needs `subtotal` and `tax`), the state argument is not enough, because its type does not include getters. You then write a **regular function** (method shorthand or `function`), in which `this` is the whole store: - `this.subtotal`, `this.tax`: other getters, cached as usual; - `this.taxRate`, `this.lines`: state; - properties that plugins add, if they are typed. An arrow function cannot do this: arrows take `this` from the surrounding scope, so `total: (state) => this.subtotal` reads `this` from the module, not the store. The `this` inside a getter is typed as state plus getters plus plugin properties; **actions are not part of that type**. That fits the rule that getters derive values and do not trigger side effects. ## Why the return type is required Pinia builds the store's type from the `getters` object, and that type is what `this` refers to inside each getter. For a getter that reads `this`: 1. the type of `this` includes `total`; 2. the type of `total` depends on what its body returns; 3. the body reads `this`, so TypeScript would need `total`'s type to compute `total`'s type. TypeScript cannot resolve that loop; Pinia's docs call it a known limitation. Writing `total(): number` breaks it: TypeScript takes the annotation as the getter's type and checks the body against it. The docs are explicit that the requirement does not affect arrow getters nor regular getters that do not use `this`. In plain JavaScript, a JSDoc `@returns {number}` gives editors the same information. ## What Pinia actually passes The pinned 4.0.3 source calls each option-store getter as `getter.call(store, store)`: **the store is both `this` and the first argument**. Only the TypeScript type narrows that argument to state. So `(state) => state.subtotal` happens to run, but it does not type-check and it relies on an implementation detail. Write state-only getters against the argument and getter-combining getters against `this`. ## Rules of thumb 1. Default to arrow getters over `state`. 2. Switch to a regular function with `this` only when another getter is needed. 3. Always annotate the return type of a `this`-based getter; lint for it in review. 4. Never call actions or write state from a getter. ## Slips interviewers listen for - **Annotating everything.** Adding return types to every arrow getter is harmless but signals the candidate does not know *why* the rule exists; the requirement is specific to getters that read `this`. - **Mixing the styles in one getter.** Declaring `total(state): number` and then reading `this.subtotal` works, but the `state` parameter is noise; pick the argument or `this`. - **Reaching for `this` to read state.** `this.lines` works in a regular getter, but if the getter reads only state, the arrow form with inferred types is simpler. - **Forgetting the JavaScript case.** Without TypeScript there is no compile error, only weaker editor support; the JSDoc `@returns` tag is the documented remedy. A good answer ties the two halves together: the choice between `state` and `this` is about what the getter reads, and the return-type rule follows from reading `this`.

  • Can a Pinia option-store getter call one of the store's actions through this?
    At runtime `this` is the whole store, so the call would run, but the getter's `this` type covers state, getters and plugin properties, not actions, and a getter that triggers an action has side effects inside a cached computed. Keep getters pure; do the work in an action and derive from its result.
  • How do you give a this-based Pinia getter a type in a plain JavaScript store?
    With a JSDoc comment on the getter, `@returns {number}`, as the Pinia docs show. It plays the role of the TypeScript annotation for editors and type-aware tooling, and doubles as documentation of what the getter returns.

saying these in an interview costs you the question

  • An arrow getter can reach other getters through this.
  • Every Pinia getter needs an explicit return type in TypeScript.
  • The state argument is typed to include the store's other getters.
  • A regular-function getter cannot read state through this.
  • The return type on a this-based getter is only a style preference.