State Management
For state shared across many distant components, Vue's official answer is Pinia. Like Zustand and Redux for React, or a service for Angular, it holds state outside the component tree so any component can reach it — but a Pinia store is built from the very same reactivity primitives you already know (ref, computed), making it feel like a composable that happens to be global.
Defining a Store
defineStore('counter', () => {...}) — the "setup store" form — is essentially a composable: declare refs (the state), computeds (derived values, called "getters"), and functions (the "actions") inside, and return them. The first argument is a unique store id. If this looks almost identical to a composable from Chapter 10, that's the point — the difference is that a Pinia store is a single shared instance, not fresh state per call.
Setup, and Using the Store
Register Pinia once with app.use(createPinia()) (the same plugin pattern as the router). Then any component calls useCounterStore() and accesses store.count, store.double, store.increment — and two completely unrelated components calling it share the exact same state, no props or events between them. This is the same problem Context/Zustand solved in React and a service solved in Angular, here with Vue's own reactivity underneath.
const { count } = useCounterStore() breaks reactivity — count becomes a disconnected snapshot (the same reactive-destructuring trap from Chapter 2). To pull out reactive state, use storeToRefs: const { count, double } = storeToRefs(store). Actions (functions) can be destructured normally — only state and getters need storeToRefs.
Data Fetching Inside a Store
An action can be async — so a store is a natural home for API calls, exactly as a service was in Angular (Chapter 11). The status ref drives loading/error/success rendering, the same single-status pattern used across the React projects. A component calls store.fetchUsers() in onMounted and reads store.users/store.status in the template — and because the result lives in the store, several components share the fetched data without re-requesting it, the same benefit React Query gave (React Advanced Chapter 2).
refs in module scope (outside the function) — and for small cases that works. Pinia adds what a real app wants on top: a single well-defined store id, devtools integration (time-travel, inspecting state), server-side-rendering support, and plugins. Reach for a store when state is genuinely global and app-significant; a plain composable stays fine for localized reusable logic.
| Vue (Pinia) | React | Angular |
|---|---|---|
| defineStore + useXStore() | Zustand create() / Context | Injectable service |
| state = ref(...) | store state | service properties |
| getters = computed(...) | derived/selectors | getters / computed |
| actions = functions | store actions | service methods |
| async action for fetching | React Query / thunk | service + HttpClient |
Coding Challenges
Set up Pinia and define a counter store (state: count; getter: double; actions: increment, reset). Use it from two unrelated components, confirming both share the same count with nothing passed between them.
📄 View solutionBuild a cart store (state: items; action addToCart with the duplicate-quantity check; getter totalItems) and use it from a product list and a separate cart-display component. Use storeToRefs to read items reactively.
📄 View solutionBuild a users store with an async fetchUsers action and a status ref (idle/loading/error/success). A component calls fetchUsers in onMounted and renders loading/error/success states from the store.
📄 View solutionChapter 12 Quick Reference
- Pinia — Vue's official global state; a store is essentially a shared, named composable
- defineStore('id', () => {...}) — state (
ref), getters (computed), actions (functions), then return them - Register once with
app.use(createPinia()); use viauseXStore()in any component - Two components using the same store share one instance — no props, no events
- storeToRefs(store) — destructure state/getters reactively (actions destructure normally)
- An async action makes a store a natural home for API calls + loading/error/success state
- Use a store for genuinely global state; a plain composable for localized reusable logic