All quizzesHard
Advanced Composition — Series 3
Preview — 3 of 10 questions
What does calling this.$onAction from inside the store's own setup accomplish here?
javascript
export const useCartStore = defineStore('cart', () => {
const isLoading = ref(false)
function setup() {
const store = getCurrentInstance() // conceptually — actual store ref used
}
const cart = /* the store instance, obtained however */ null
// Registered once, inside the store itself
cart?.$onAction(({ name, after, onError }) => {
isLoading.value = true
after(() => { isLoading.value = false })
onError(() => { isLoading.value = false })
})
return { isLoading /* , actions... */ }
})AThis wires up a single, centralized isLoading flag that automatically flips on when any action on this store starts, and flips back off when that action finishes or errors — every action on the store gets this loading-state tracking for free, without each individual action needing to manually set isLoading.value = true/false around its own body, and without any risk of an action forgetting to reset the flag in an error path
B$onAction can only be called from outside a store, by a consumer — calling it from within the store's own definition is invalid and throws
C$onAction here only fires for actions called from outside the store, so calling one action from within another (as covered elsewhere) wouldn't trigger it
DRegistering $onAction from inside the store causes it to fire twice for every action call — once for the internal registration and once for any external subscriber
Why does the second version work correctly while the first silently fails to update anything observers can see reactively update as expected?
javascript
// Version A — reassigns inside the callback
cart.$patch((state) => {
state.items = state.items.filter((i) => i.id !== removedId)
})
// Version B — also reassigns, seemingly the same way
cart.$patch((state) => {
const filtered = state.items.filter((i) => i.id !== removedId)
state.items = filtered
})ATrick question — both versions are functionally identical and both work correctly; there's no meaningful difference between them
BOnly version A is actually broken — assigning directly within the same statement that computes the filtered array fails silently, while extracting it to a local variable first (version B) fixes it
CNeither version actually works — $patch's function form requires mutating array methods like .splice(), and any reassignment of state.items to a brand-new array (whether inline or via an intermediate variable) is not tracked, regardless of exactly how the new array is constructed
DBoth versions work identically and correctly — $patch's function-form callback receives the store's actual live reactive state object, so assigning state.items = filtered (in either version) is a completely ordinary reactive property assignment on that object, tracked exactly the same way as if the assignment happened anywhere else in the store
What does defineGenericListStore<T> let an app do that writing separate, near-identical stores for each resource type wouldn't?
javascript
function defineGenericListStore<T extends { id: string }>(storeId: string) {
return defineStore(storeId, () => {
const items = ref<T[]>([])
const isLoading = ref(false)
function upsert(item: T) {
const idx = items.value.findIndex((i) => i.id === item.id)
if (idx === -1) items.value.push(item)
else items.value[idx] = item
}
return { items, isLoading, upsert }
})
}
const useProductsStore = defineGenericListStore<Product>('products')
const useOrdersStore = defineGenericListStore<Order>('orders')AThis is invalid — defineStore's id must be a compile-time string literal, so wrapping it in a generic factory function like this doesn't type-check
BBoth generated stores end up sharing the exact same underlying items array — the generic type parameter only affects TypeScript's static checking, not runtime behavior, so products and orders would actually collide into one shared list
CdefineGenericListStore<T> captures the common shape (a list of items with upsert logic, a loading flag) once, generically, and each call produces a genuinely separate store — with storeId keeping them distinct at the Pinia level, and the type parameter T giving each one correct, distinct TypeScript typing for its own items array (Product[] vs Order[]) — avoiding writing near-duplicate store definitions for every resource type that shares this same basic "list + upsert" shape
DThis pattern only works if Product and Order share a common base interface beyond just { id: string } — otherwise upsert can't be shared between them
Sign up free to play
Answer all 10 questions (7 more), see explanations for every answer, and track your score.