Store Composition — Series 3

Preview — 3 of 10 questions

Why watch itemCount specifically here, rather than watching the whole cart store object?

javascript
const cart = useCartStore()
const { itemCount } = storeToRefs(cart)

watch(itemCount, (newCount) => {
  if (newCount === 0) showEmptyCartBanner()
})
Awatch(itemCount, ...) and watch(cart, ...) are functionally identical in every respect — this is purely a stylistic choice
BstoreToRefs is required before watch can be used on any store field at all — watching cart.itemCount directly (without destructuring through storeToRefs) always throws
CWatching a single ref like this only fires once, on initial setup, and never again — a deep watch on the whole store is the only way to react to ongoing changes
DWatching itemCount specifically (a single ref extracted via storeToRefs) means the callback only fires when that particular field changes — watching the whole cart store object with { deep: true } would fire on a change to any state field the store has (price, discount, shipping address, anything), forcing the callback to re-check whether the specific thing it cares about actually changed. Narrowing the watch target to exactly the relevant field keeps the intent explicit and avoids unnecessary callback invocations for unrelated state changes

What does the flush option change about when this callback runs?

javascript
cart.$subscribe((mutation, state) => {
  document.title = `Cart (${state.itemCount})`
}, { flush: 'sync' })
Aflush: 'sync' has no effect on $subscribe's timing — it's a no-op option kept only for API symmetry with watch
Bflush: 'sync' makes the store's own state updates synchronous — without it, state changes are asynchronous and reading cart.itemCount immediately after a mutation would return the stale value
Cflush: 'sync' disables the subscription entirely during server-side rendering, re-enabling it only once hydration completes
DBy default, $subscribe callbacks run after Vue's next DOM update tick (matching component update timing), batching multiple synchronous mutations within the same tick into a single callback invocation. { flush: 'sync' } opts into running the callback synchronously and immediately after every single mutation, without waiting for that batching — useful when the side effect (like updating document.title here) genuinely needs to reflect every individual change the instant it happens, at the cost of potentially running many more times for a burst of rapid mutations

Why does this paths configuration matter for a store holding an auth token?

javascript
export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null,
    token: null, // sensitive — shouldn't be written to persistent storage
    lastActivityAt: null,
  }),
  persist: {
    paths: ['user', 'lastActivityAt'], // token deliberately omitted
  },
})
Apaths has no real effect on security — anything in a Pinia store's state is equally exposed regardless of whether it's persisted or not, since it's already sitting in memory
Bpaths controls which fields are readable from components, not which fields are persisted — persistence always includes the entire state object regardless of this option
COmitting token from paths means the persistence plugin only reads and writes user and lastActivityAt to localStorage (or whichever storage the plugin is configured for) on every persisted state change — token stays in memory only, for the current tab's session, and is never written to a storage mechanism that (unlike memory) can be read by other scripts on the same origin, inspected via browser devtools' Application/Storage tab even after the tab closes, or persist across a full browser restart
DListing specific paths like this actually persists more data than the default — omitting paths entirely would persist nothing at all

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.