Pinia State Management
Introduction
Pinia is Vue’s official store for shared application state—user session, shopping cart, UI preferences. Stores hold state, derive getters, and run actions (including async API calls). This chapter sets up Pinia, uses defineStore, and explains when Pinia beats props, composables, or provide/inject.
Prerequisites
- Composables
- Reactivity: ref and reactive
- create-vue project with Pinia enabled (or
npm install pinia)
Setup
src/main.ts:
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount("#app");Order: Pinia before router if guards read stores.
defineStore (Setup Syntax)
src/stores/counter.ts:
CounterDisplay.vue:
Code explanation:
storeToRefskeepscountreactive when destructured- Destructure actions directly from store—they are not refs
- Store id
"counter"must be unique
Options Store Syntax (Alternative)
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment(step = 1) {
this.count += step;
},
},
});Setup syntax aligns with Composition API—preferred in this track.
Async Actions
src/stores/posts.ts:
<script setup lang="ts">
import { onMounted } from "vue";
import { storeToRefs } from "pinia";
import { usePostsStore } from "@/stores/posts";
const store = usePostsStore();
const { items, loading, error } = storeToRefs(store);
onMounted(() => {
store.fetchPosts();
});
</script>Auth Store Sketch
Use in Router guards.
$patch and Reset
Batch updates (Options store style):
store.$patch({ count: 5 });
store.$patch((state) => {
state.count++;
});Reset to initial state:
store.$reset();Pinia vs Other Patterns
| Pattern | When |
|---|---|
| Local ref in component | UI state only that page needs |
| Composable | Shared logic; each instance separate |
| provide/inject | Theme, form wizard subtree |
| Pinia | User, cart, notifications—many routes/components |
Pinia vs Vuex
Vuex was the Vue 2-era store. Pinia is simpler—no mutations, better TypeScript, DevTools support. New projects use Pinia; maintain Vuex code with official migration guide.
Persistence (Concept)
pinia-plugin-persistedstate saves selected stores to localStorage:
// concept—install plugin per docs
persist: {
paths: ["theme"],
}Never persist refresh tokens insecurely; know XSS implications.
DevTools
Pinia integrates with Vue DevTools—inspect state, time-travel debug actions.
Practice Exercise
Build useTodoStore:
- State:
todos,filter - Getters:
filteredTodos,activeCount - Actions:
addTodo,toggleTodo,removeTodo
Connect to Project Todo.
FAQ
storeToRefs vs store.count?
store.count works in template; destructuring needs storeToRefs for state/getters.
Pinia outside setup?
useStore() must run after app.use(pinia)—works in router guards after app created.
Multiple stores?
Yes—useCartStore, useUserStore, separate files.
SSR?
Nuxt handles Pinia SSR—SSR intro.
Mutations?
Pinia has no mutations—change state in actions directly.
Test stores?
Create Pinia test instance with createPinia() and setActivePinia in Vitest.