State Management in Vue 3 with Pinia
Effective state management becomes increasingly important as a Vue application grows. Pinia is the recommended state-management library for Vue and provides a compact API, strong TypeScript support, and good integration with the Composition API.
This guide covers the basic setup and the core concepts of state, getters, and actions.
1. What Is Pinia?
Pinia is a state-management library designed for Vue applications. Compared with older Vuex patterns, it generally offers:
- A smaller and more approachable API.
- First-class Composition API support.
- Strong TypeScript inference.
- Less boilerplate for common store patterns.
Pinia can be used in both small and large Vue applications when state needs to be shared across components or routes.
2. Install Pinia in Vue 3
Install the package:
npm install piniaThen register it with your Vue application:
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');Pinia stores are now available throughout the application.
3. Create Your First Store
Create a counter store:
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++;
}
}
});The three main concepts are:
- State: reactive data owned by the store.
- Getters: derived values calculated from state.
- Actions: methods that update state or coordinate application logic, including asynchronous work.
4. Access the Store from a Component
<template>
<div>
<p>Count: {{ counter.count }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>
<script setup>
import { useCounterStore } from './stores/counter';
const counter = useCounterStore();
</script>The store is reactive, so the component rerenders automatically when counter.count changes.
5. Use Getters for Derived State
<template>
<div>
<p>Double Count: {{ counter.doubleCount }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from './stores/counter';
const counter = useCounterStore();
</script>A getter is useful when a value can be derived from existing state instead of stored separately.
6. Update State with Actions
Actions can contain synchronous or asynchronous logic:
// stores/counter.js
actions: {
async incrementAfterDelay() {
await new Promise((resolve) => setTimeout(resolve, 1000));
this.count++;
}
}Calling the action keeps mutation logic close to the store instead of spreading it across components.
7. Best Practices
- Organize by domain: split stores by feature or business area rather than creating one global store for everything.
- Use TypeScript when it helps: Pinia’s inference works particularly well with typed applications.
- Keep local state local: not every value belongs in a global store.
- Avoid duplicating derived values: use getters or computed values when data can be calculated from existing state.
Conclusion
Pinia provides a clean state-management model for Vue 3. Its state, getters, and actions are easy to compose into application-level stores while still allowing components to keep truly local state close to where it is used.