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

Setup

src/main.ts:

typescript
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:

  • storeToRefs keeps count reactive when destructured
  • Destructure actions directly from store—they are not refs
  • Store id "counter" must be unique

Options Store Syntax (Alternative)

typescript
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:

vue
<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):

typescript
store.$patch({ count: 5 });
store.$patch((state) => {
  state.count++;
});

Reset to initial state:

typescript
store.$reset();

Pinia vs Other Patterns

PatternWhen
Local ref in componentUI state only that page needs
ComposableShared logic; each instance separate
provide/injectTheme, form wizard subtree
PiniaUser, 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:

typescript
// 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.