State Management with Pinia
Share state across unrelated components cleanly using Pinia, Vue's official state management library.
Why a State Management Library
provide/inject solves sharing state within one component subtree. But state that many unrelated parts of an app need — a logged-in user, a shopping cart, a theme preference — needs a more global, structured solution. Pinia is Vue's official answer.
Installing Pinia
npm install pinia// src/main.jsimport { createApp } from 'vue';import { createPinia } from 'pinia';import App from './App.vue';
createApp(App).use(createPinia()).mount('#app');Defining a Store
A store is defined with defineStore, and — using the "setup store" syntax — looks almost exactly like a composable, using ref() for state and plain functions for actions.
// stores/cart.jsimport { ref, computed } from 'vue';import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', () => { const items = ref([]);
const totalPrice = computed(() => items.value.reduce((sum, item) => sum + item.price, 0) );
function addItem(item) { items.value.push(item); }
function removeItem(id) { items.value = items.value.filter((item) => item.id !== id); }
return { items, totalPrice, addItem, removeItem };});Using a Store in a Component
Any component calls the store's composable-style function to get access to its shared, reactive state — every component calling useCartStore() gets the same underlying store instance.
<script setup>import { useCartStore } from '../stores/cart';
const cart = useCartStore();</script>
<template> <p>Total: {{ cart.totalPrice }}</p> <ul> <li v-for="item in cart.items" :key="item.id"> {{ item.name }} <button @click="cart.removeItem(item.id)">Remove</button> </li> </ul></template>Unlike a composable called fresh in each component (which creates independent state), a Pinia store is a singleton per app — calling useCartStore() anywhere returns the exact same shared instance.
Common Beginner Mistakes
const { items } = cart loses reactivity, exactly like destructuring a plain reactive() object — use storeToRefs(cart) if you need to destructure while keeping reactivity.
Local component state (like whether a dropdown is open) usually belongs in the component itself with ref() — reserve Pinia for state genuinely shared across multiple, unrelated components.
FAQs
Pinia is the official successor to Vuex — it's simpler, has better TypeScript support, and is the recommended state library for all new Vue 3 projects.
Yes — most apps have several, one per feature/domain (a cart store, a user store, a settings store), rather than one giant combined store.
Summary
Pinia gives you a shared, singleton store per concern, defined with syntax that closely mirrors a composable. Next, you'll look at organizing larger stores with actions and getters more formally.