State Management with Pinia
Vue.js Development
State Management with Pinia
Pinia is the official state management library for Vue, replacing Vuex as the recommended solution. It provides a simple, type-safe way to share state across components without prop drilling or event buses. Pinia stores are defined with a clean API that feels like writing a regular Vue component, with state (like data), getters (like computed), and actions (like methods). Pinia integrates with Vue DevTools, supports hot module replacement, and has first-class TypeScript support. It is designed for Vue 3 but also works with Vue 2.
Setting Up Pinia
Install Pinia and register it as a Vue plugin. Then define stores in separate files.
// Install: npm install pinia
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App);
app.use(createPinia());
app.mount("#app");
Defining a Store
Stores are defined using defineStore. You can use either the Options Store syntax (familiar to Options API users) or the Setup Store syntax (similar to Composition API).
// stores/counter.js — Options Store syntax
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
lastIncrement: null,
}),
getters: {
doubleCount: (state) => state.count * 2,
isPositive: (state) => state.count > 0,
},
actions: {
increment() {
this.count++;
this.lastIncrement = new Date();
},
decrement() {
this.count--;
},
async incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 1000));
this.increment();
},
},
});
// stores/user.js — full example
export const useUserStore = defineStore("user", {
state: () => ({
currentUser: null,
isAuthenticated: false,
token: null,
}),
getters: {
userName: (state) => state.currentUser?.name || "Guest",
userRole: (state) => state.currentUser?.role || "viewer",
isAdmin: (state) => state.currentUser?.role === "admin",
},
actions: {
async login(email, password) {
try {
const response = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
this.currentUser = data.user;
this.token = data.token;
this.isAuthenticated = true;
localStorage.setItem("token", data.token);
} catch (error) {
console.error("Login failed:", error);
throw error;
}
},
logout() {
this.currentUser = null;
this.token = null;
this.isAuthenticated = false;
localStorage.removeItem("token");
},
},
});
Using Stores in Components
Call the store's composable function inside your component to access state, getters, and actions. Use storeToRefs to destructure reactive state properties without losing reactivity.
<template>
<div>
<h2>Welcome, {{ userStore.userName }}</h2>
<p>Count: {{ counterStore.count }}</p>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">+1</button>
<button @click="counterStore.decrement()">-1</button>
<button @click="counterStore.incrementAsync()">+1 (async)</button>
<button @click="handleLogout">Logout</button>
</div>
</template>
<script>
import { useCounterStore } from "../stores/counter";
import { useUserStore } from "../stores/user";
export default {
setup() {
const counterStore = useCounterStore();
const userStore = useUserStore();
return { counterStore, userStore };
},
methods: {
handleLogout() {
this.userStore.logout();
this.$router.push("/login");
},
},
};
</script>
Tip: Keep stores focused on a single domain. Create separate stores for auth, cart, notifications, and other concerns rather than putting everything in one giant store. Stores can import and use other stores via their composable functions.
Key Takeaways
- Pinia is Vue's official state management library, replacing Vuex with a simpler API.
- Stores have state (reactive data), getters (computed values), and actions (methods).
- Use
defineStorewith Options or Setup syntax to create stores. - Actions can be async and directly mutate state without the ceremony of mutations.
- Keep stores focused on a single domain and compose them by importing one store into another.