Pinia — State Management
What is Pinia?
Pinia is the official state management library for Vue 3. It replaces Vuex with a simpler, more intuitive API. Pinia stores are like components without a template — they hold reactive state that any component can access.
- State — reactive data (like
data()in components) - Getters — computed values derived from state
- Actions — methods that modify state (can be async)
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup store (Composition API style — recommended)
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
const history = ref([])
// Getters (computed)
const doubleCount = computed(() => count.value * 2)
const isPositive = computed(() => count.value > 0)
// Actions
function increment() {
count.value++
history.value.push(`+1 → ${count.value}`)
}
function decrement() {
count.value--
history.value.push(`-1 → ${count.value}`)
}
function reset() {
count.value = 0
history.value = []
}
function incrementBy(amount) {
count.value += amount
}
return { count, history, doubleCount, isPositive, increment, decrement, reset, incrementBy }
})
// Options store (Options API style)
export const useCounterOptionsStore = defineStore('counterOptions', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() { this.count++ },
async fetchCount() {
const res = await fetch('/api/count')
this.count = await res.json()
}
}
})
// stores/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const loading = ref(false)
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => user.value?.name || 'Guest')
async function login(email, password) {
loading.value = true
try {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const data = await res.json()
user.value = data.user
token.value = data.token
localStorage.setItem('token', data.token)
} finally {
loading.value = false
}
}
function logout() {
user.value = null
token.value = null
localStorage.removeItem('token')
}
return { user, token, loading, isLoggedIn, userName, login, logout }
})
<template>
<div>
<!-- Counter store -->
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment()">+1</button>
<button @click="counter.decrement()">-1</button>
<button @click="counter.reset()">Reset</button>
<!-- Auth store -->
<p v-if="auth.isLoggedIn">Hello, {{ auth.userName }}!</p>
<button v-if="!auth.isLoggedIn" @click="auth.login('alice@example.com', 'password')">
Login
</button>
<button v-else @click="auth.logout()">Logout</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { useAuthStore } from '@/stores/auth'
// Use stores — reactive, auto-updates template
const counter = useCounterStore()
const auth = useAuthStore()
// Destructure with storeToRefs (preserves reactivity)
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(counter)
// Actions can be destructured directly (not reactive)
const { increment, reset } = counter
</script>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.