Pinia: state management in Vue.js
What is Pinia?
Pinia is the official state management library for Vue.js (replacing Vuex). It provides a simple, type-safe way to manage global state with full Composition API and DevTools support.
Defining a Store
typescript
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Composition API style (recommended)
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
const name = ref('Counter')
// Getters (computed)
const doubleCount = computed(() => count.value * 2)
// Actions (functions)
function increment() {
count.value++
}
function decrement() {
count.value--
}
async function fetchCount() {
const response = await fetch('/api/count')
count.value = await response.json()
}
return { count, name, doubleCount, increment, decrement, fetchCount }
})Options API Style
typescript
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
email: '',
isLoggedIn: false,
}),
getters: {
initials: (state) => state.name.split(' ').map(n => n[0]).join(''),
},
actions: {
async login(email: string, password: string) {
const user = await api.login(email, password)
this.name = user.name
this.email = user.email
this.isLoggedIn = true
},
logout() {
this.$reset() // Resets state to initial values
},
},
})Using a Store in Components
vue
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
// ✅ Use storeToRefs for reactive destructuring
const { count, doubleCount } = storeToRefs(counterStore)
// Actions can be destructured directly
const { increment, decrement } = counterStore
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>storeToRefs
typescript
// ❌ Destructuring loses reactivity
const { count } = useCounterStore()
// count is just a number, not reactive
// ✅ storeToRefs preserves reactivity
const { count } = storeToRefs(useCounterStore())
// count is a Ref<number>, reactiveStore-to-Store Communication
typescript
// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
function checkout() {
const userStore = useUserStore() // Use another store
if (!userStore.isLoggedIn) {
throw new Error('Must be logged in')
}
// Process checkout...
}
return { items, checkout }
})Pinia vs Vuex
| Feature | Pinia | Vuex |
|---|---|---|
| API style | Composition + Options | Options only |
| Mutations | ❌ Not needed | Required |
| TypeScript | ✅ Full support | ⚠️ Complex typing |
| Modules | Flat stores (simpler) | Nested modules |
| DevTools | ✅ Full support | ✅ Full support |
| Bundle size | ~1.5KB | ~10KB |
| Vue 2 support | Via plugin | Native |
Important:
Pinia is the recommended state management for Vue 3. Use the Composition API style (defineStore with setup function) for best TypeScript support. Always use storeToRefs() when destructuring state/getters to preserve reactivity. Pinia is simpler than Vuex — no mutations, flat stores, and excellent TypeScript inference.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.