Using Stores in Components: Core Concepts
Obtain the store once in setup
Call a Pinia store function within component setup and reuse that instance in the component. Pinia returns the store associated with the active Pinia instance, so multiple components in the same app share the same store state. Calling the function once makes dependencies and subscriptions easier to inspect. Outside components, ensure the correct Pinia instance has been installed or passed explicitly, especially in tests and SSR.
Store properties are reactive, but destructuring them directly into local variables can lose reactivity because the local binding no longer reads through the store proxy. Use storeToRefs(store) for state and getters that need to be destructured. Actions are functions and can be destructured directly. Alternatively, keep the store object and access store.profile in the template or computed values.
Keep view behavior in the component
The component decides how to present pending, error, empty, and success states. The store owns shared state and domain transitions. A view can call an action in an event handler and render its result; it should not duplicate the action's request lifecycle. Route navigation, focus, and local dialog state usually remain with the component unless they are genuinely shared domain behavior.
Use storeToRefs selectively. Destructure only the values the component needs so dependencies remain clear. Avoid converting the entire store into another reactive object or copying state into local refs without a synchronization rule. A local draft is appropriate for editing before save, but name it as a draft and define when it resets from the store.
Share state intentionally
Two components calling the same store function use the same store instance within a Pinia app. This is useful for a toolbar count and page content to remain synchronized. It also means one component's reset action affects the other. Before adding a store, decide whether the state truly has multiple owners and a shared lifetime. A local component value should not be global merely because two views might eventually need it.
For SSR, each request must have its own Pinia instance. A store accessed before the request-specific instance is active can attach to the wrong state. Avoid importing a store and calling it at module initialization. In tests, create a fresh Pinia per test and install it before mounting or calling the store.
Handle subscriptions carefully
Pinia subscriptions and action hooks can observe changes for analytics or persistence. Register component-lifetime subscriptions with the appropriate detached setting and unsubscribe when the owner ends. A global persistence plugin may intentionally outlive a component, but it needs app-level ownership and privacy rules. Do not persist sensitive state simply because a store is reactive.
Stores are most useful when state must be shared, inspected, or coordinated across components. Component usage stays clear when it obtains one store, preserves ref reactivity with storeToRefs, and leaves presentation local. This separation makes both the store and view independently testable.
If a store is called before Pinia is installed, the active instance may be missing or incorrect. Keep store access inside setup or pass the intended instance explicitly in infrastructure code.
Using Stores in Components: Practice
Destructure without losing updates
Create a cart store with items, total, and addItem. In a cart badge component, call useCartStore once and use storeToRefs to obtain items and total. Destructure addItem directly because it is an action function. Update the store from a button handler and verify both the badge and cart page reflect the same state without manually emitting a synchronization event.
As a deliberate experiment, destructure total directly from the store and then update items. Observe whether the local binding tracks changes. Restore storeToRefs and verify the computed total updates. This demonstrates the proxy boundary and gives a concrete reason for the utility rather than relying on a memorized rule.
Keep drafts separate
An edit form may need a local draft initialized from store state. Copy the relevant values on entry, validate the draft, and dispatch an action on save. Decide what happens if the store changes while the draft is open: overwrite, show a conflict, or preserve the local edits. Avoid binding a form directly to shared store fields when users expect Cancel to discard changes.
Pending and error presentation belongs in the component. Call an action from submit, disable or indicate the action while pending, and render a useful error. The store can expose pending and error state if multiple views need it. If the state is local to one form, component refs may be simpler than globalizing it.
Test shared consumers
Mount a badge and a cart view with the same Pinia instance. Add an item from one component and assert both update. Unmount the badge and verify the store remains available to the cart. Create a fresh Pinia for a separate test and confirm it starts from initial state. This validates shared behavior without leaking state across tests.
For SSR, render two app instances with different initial carts and verify the results do not cross. Ensure the store is created only after the correct Pinia instance is active. Avoid module-level calls such as const cart = useCartStore() because they can capture a global or missing instance.
Avoid redundant subscriptions
Calling the store multiple times in one setup typically returns the same store, but keep one local binding for readability. Avoid adding a watch that copies every store property into a local ref just to make it reactive; storeToRefs already provides refs to the same state. Add a watcher only for a side effect, and stop component-scoped subscriptions when the component ends.
This approach keeps shared state authoritative and view-only state local. Consumers react to the store, while drafts, focus, dialogs, and presentation remain in components. The boundary reduces synchronization bugs and makes ownership visible.
When a form edits shared data, use a local draft and commit through an action. This prevents Cancel from mutating the shared record and lets validation occur before the store transition.
Repeat the test after navigation to confirm the intended store state remains shared across every mounted consumer. Keep this assertion in the component integration suite.
Using Stores in Components: Trade-offs and Interviews
Avoid globalizing local state
A Pinia store can make shared state easy to access, but global state increases coupling and lifetime. A modal's open flag or an unsaved form draft usually belongs to the component. A cart used by a header, checkout, and product page may justify a store. Choose based on shared ownership, not convenience alone.
Direct destructuring of store properties loses the reactive proxy connection for primitive fields. storeToRefs preserves reactive state and getters; actions can be destructured directly. Do not copy store values into local refs unless the component intentionally owns a draft or snapshot and has a synchronization rule.
Respect app and component lifetimes
Shared state changes affect all consumers. A reset action can clear another view's data, so define when reset occurs and communicate that in the store API. SSR requires a Pinia instance per request, and tests require fresh instances. Subscriptions have their own lifetime; unsubscribe from component-scoped observers and assign global persistence to an app-level owner.
Use devtools and store subscriptions for diagnosis, but do not let analytics or persistence plugins expose sensitive values. A store is not an authorization boundary. The server must validate every protected operation regardless of what the client state says.
In an interview, show how a component obtains a store, preserves reactivity, invokes an action, and keeps presentation local. Explain a case where a local draft should not mutate shared state before save. Mention SSR and test isolation. This demonstrates practical ownership rather than just syntax for storeToRefs.
Component subscriptions are a common source of duplicate handlers. If a component uses $subscribe or $onAction for a local effect, retain the stop handle and dispose it with the component scope. Detached subscriptions intentionally outlive the component and should be owned by an app-level service. Avoid registering a subscription on every render or route activation without removing the prior one.
Store state and local UI state have different lifetimes. A selected tab, dialog visibility, or temporary input draft usually remains in a component. Shared cart contents, session state, or cross-route workflow may belong in a store. Keep local presentation state out of a global store unless another consumer truly needs it; otherwise it persists beyond the view and makes navigation behavior surprising.
For asynchronous actions, render pending and error states from the store only when they are shared. If two components can invoke the same action concurrently, scope pending state by operation or request key rather than one boolean that hides one request's completion. Components should not copy a store's entire status into local refs; use storeToRefs for the reactive contract and derive local display values with computed.
Test the component with a fresh Pinia instance and assert user-visible behavior. A component test should not depend on state left by a previous test or on the order tests happen to run. For SSR, create Pinia per app request and ensure the component receives the matching instance. These boundaries keep shared state useful without making every view coupled to global setup.
Test two consumers together and verify unmounting one does not stop the other or register duplicate listeners.
Quiz
1. Which statement about Using Stores in Components is accurate?
2. When reviewing Using Stores in Components, which point must hold?
3. Which Pinia values should normally be wrapped with storeToRefs before destructuring?
4. How should separate SSR requests obtain user-specific Pinia state?
Flashcards
Question
What is Using Stores in Components?
Click to reveal answer
Answer
Destructure store fields while keeping reactivity intact.
Question
Using Stores in Components: why it matters
Click to reveal answer
Answer
Calling the store once at setup top level and reusing it avoids duplicate subscriptions.
Question
Using Stores in Components: key detail
Click to reveal answer
Answer
Destructuring a store directly loses reactivity unless storeToRefs is applied first.
Question
Using Stores in Components: practical note
Click to reveal answer
Answer
Action functions can be destructured directly because they are not reactive values.
Question
Which Pinia values should normally be wrapped with storeToRefs before destructuring?
Click to reveal answer
Answer
storeToRefs preserves reactivity for state and getters; actions can be destructured directly.
Revision Notes
Key Takeaways
- 1. Calling the store once at setup top level and reusing it avoids duplicate subscriptions.
- 2. Destructuring a store directly loses reactivity unless storeToRefs is applied first.
- 3. Action functions can be destructured directly because they are not reactive values.
Interview Tips
- • Explain Using Stores in Components with one concrete example from real vue work, not a textbook restatement.
- • Discuss the trade-off: when Using Stores in Components helps and when a lighter approach wins.
- • Place Using Stores in Components inside the State Management with Pinia phase — interviewers check that you see the progression, not isolated facts.
Cheat Sheet
Using Stores in Components Cheat Sheet
Phase: State Management with Pinia | Level: intermediate
Key points:
- Calling the store once at setup top level and reusing it avoids duplicate subscriptions.
- Destructuring a store directly loses reactivity unless storeToRefs is applied first.
- Action functions can be destructured directly because they are not reactive values.
- Two components using the same store share one state instance automatically.
Tags: vue, pinia, components
Continue Learning
Before You Start
- No prerequisites listed.