In Pinia, how does an invoice store's getter read a settings store's tax-rate getter, and what keeps that cross-store getter cached and bound to the right pinia?
answer
- call useSettingsStore inside the getter
- top of setup for setup stores
- dependencies tracked across stores
- active pinia set before each getter
- no mutual reads in setup bodies
basics
~20 sCall useSettingsStore() inside the getter, or at the top of a setup store, and read settings.taxRate. The computed tracks that read, so it recomputes when the rate changes, and Pinia activates the invoice store's own pinia before each getter runs.
solid answer
~50 sIn an option store, call the other store inside the getter body: `tax(): number { const settings = useSettingsStore(); return this.subtotal * settings.taxRate }`. In a setup store, call `useSettingsStore()` once at the top of the setup function and read it inside a `computed()`. Either way the getter's computed tracks `settings.taxRate` like any reactive read, so it stays cached and recomputes on the next read after the rate changes; no `$subscribe` or copying is needed. Pinia also sets the store's own pinia as active before running each option-store getter, so `useSettingsStore()` inside it resolves in the same pinia as the invoice store even when another pinia is active, which matters with several apps or per-request pinias on a server. The one forbidden pattern is two setup stores that both read each other's state directly in their setup bodies; reads inside getters and actions are fine.
code
ts · 16 linesimport { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useSettingsStore } from './settings'
export const useInvoiceStore = defineStore('invoice', () => {
const settings = useSettingsStore() // top of setup: allowed
const lines = ref<{ qty: number; unitPrice: number }[]>([])
const subtotal = computed(() =>
lines.value.reduce((sum, l) => sum + l.qty * l.unitPrice, 0),
)
// read inside computed: tracked, cached, updates with the rate
const total = computed(() => subtotal.value * (1 + settings.taxRate))
return { lines, subtotal, total }
})go deeper
Know that a getter can call another store's useStore() and read its state or getters directly.
Explain where to call useStore() in option and setup stores, and why the computed tracks the other store's values automatically.
Explain the active-pinia guarantee for getters, avoid module-level useStore() and state copies, and apply the no-mutual-setup-reads rule.
Shape store boundaries so cross-store getters form a clear direction of dependency instead of a web of mutual reads.
## Reading another store from a getter Stores can use each other freely; Pinia's cookbook calls this **composing stores**. For a getter the recipe depends on the store style. | Store style | Where to call `useSettingsStore()` | How the getter reads it | |---|---|---| | Option store | inside the getter body | `settings.taxRate` in the same body | | Setup store | once, at the top of the setup function | inside a `computed()` | ```ts // option store getters: { tax(): number { const settings = useSettingsStore() return this.subtotal * settings.taxRate }, } // setup store const settings = useSettingsStore() const tax = computed(() => subtotal.value * settings.taxRate) ``` `settings.taxRate` may itself be a getter of the settings store, for example one that picks the rate for the customer's region; it is read the same way. ## Why it stays cached and reactive A getter is a `computed`, and a computed tracks **every** reactive read made while it runs, whichever store owns the value: - the read of `this.subtotal` links it to the invoice store; - the read of `settings.taxRate` links it to the settings store; - a change in either marks the getter stale, and the next read recomputes once. So there is nothing to wire up: no `$subscribe`, no watcher, no copy of the rate in the invoice store. Copying the rate into invoice state is in fact the mistake to avoid: the copy is a snapshot that stops following the settings store. ## Why it binds to the right pinia `useSettingsStore()` has to decide **which pinia** to use. Inside a component it finds the app's pinia through injection; elsewhere it falls back to the **active pinia**. Pinia makes getters safe here: before running an option-store getter, it sets the invoice store's own pinia as active. Pinia's own tests check the case of two pinias, where a getter still reads the other store from its own pinia although a different one was activated last. This matters wherever more than one pinia exists: - several Vue apps on one page, each with its own pinia; - server rendering, where each request gets a fresh pinia; - tests that create a new pinia per test. In a setup store, `useSettingsStore()` runs at the top of setup, which Pinia executes while creating the store for its own pinia, so the same guarantee holds. What is not safe is calling `useSettingsStore()` at a module's top level, outside any store or component: there may be no active pinia yet, or the wrong one. ## The one rule: no mutual reads in setup bodies Two stores may use each other. What they cannot do is both read each other's state **directly in their setup function body**, because each store's setup would need the other to be finished first. The composing-stores cookbook marks it as not possible and shows the fix beside it: 1. call the other store's `useStore()` at the top of setup (that is allowed on both sides); 2. read its properties only inside `computed()` getters and actions, which run later; 3. in option stores, the same holds naturally, since getters only run when read. ## Checklist - Other stores are read inside getters (option) or computeds (setup), never copied into state. - `useXStore()` is called in the getter body or at the top of setup, never at module level. - Mutual store use keeps reads out of setup bodies. - Cross-store getters stay pure: they read the other store, they never call its actions. ## Debugging a total that ignores the new tax rate When an invoice total keeps showing the old rate after the settings screen saved a new one, work through the likely causes in order: 1. **Is the rate copied?** Search the invoice store for a `taxRate` field in `state()` or a `ref()` seeded from the settings store. A copy is a snapshot; read the settings store in the getter instead. 2. **Is the read outside the getter?** A value read at the top of a setup store's body, or in a module, is read once. Move the read into the `computed()`. 3. **Are there two pinias?** In tests, or with several apps, the settings screen may be writing to a different pinia's settings store. Check that both components use the same app, and that tests create one pinia and activate it before using either store. 4. **Is the settings value really state?** A settings setup store that returns a plain value instead of a `ref` exposes a copy on the store: when its own actions update the local variable, the store property never changes, so the getter has nothing new to see. Each of these fails silently, which is why interviewers like the question: the fix is always to let the getter read the live value.
- Why is copying the settings store's tax rate into invoice state a bug?State holds a value, not a link. `taxRate: useSettingsStore().taxRate` in `state()` captures the rate once, when the invoice store is created; later changes in settings never reach it, and every total stays on the old rate. Reading `settings.taxRate` inside the getter keeps the dependency live and cached.
- Two Pinia setup stores need each other; what exactly must they avoid?Both reading each other's state directly in their setup function body, for example `const x = useX(); x.name` at the top level of `useY`'s setup while `useX` does the same. Calling each other's `useStore()` at the top is fine; the reads must move into `computed()` getters or actions, which run after both stores exist.
saying these in an interview costs you the question
- A getter must $subscribe to another store to notice its changes.
- Copying another store's value into state keeps it in sync.
- useSettingsStore() should be called once at module top level and reused.
- Two stores can never use each other in Pinia.
- A cross-store getter is not cached, because it reads outside its store.