Pinia is the official state management system for Vue.js, designed to be intuitive, flexible, and TypeScript-friendly.
Pinia was created by Eduardo San Martin Morote, a member of the core Vue.js team, and became the official state management solution for Vue 3 in November 2021.
Previously, Vuex was used as the state management library. But the syntax was more complicated.
Specifically designed for the new features of Vue 3, Pinia leverages all the capabilities this version of the framework offers, with better TypeScript support and a smoother integration with the Composition API.
Installation and Configuration of Pinia
To get started, we need to install Pinia in our project. If you haven’t added it yet, you can do so by running:
npm install piniaOnce installed, we need to configure Pinia in our Vue application. In the main file (main.js or main.ts), import and use Pinia:
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');Creating a Store in Pinia
In Pinia, a store is a reactive container that holds the state and business logic.
Let’s see how to create a basic store with a simple example that would manage user state.
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
    currentUser: null,
  }),
  actions: {
    async fetchUsers() {
      const response = await fetch('/api/users');
      this.users = await response.json();
    },
    setCurrentUser(user) {
      this.currentUser = user;
    },
  },
  getters: {
    activeUsers: (state) => state.users.filter(user => user.isActive),
  },
});In this example, we have,
- State: 
usersis an empty array andcurrentUserisnull. - Actions: 
fetchUsersfetches users from an API andsetCurrentUsersets the current user. - Getters: 
activeUsersfilters active users. 
Parts of a Store
We have just seen how the store is composed of certain parts or “sections” (all optional). These parts are,
| Property | Description | 
|---|---|
state | Defines the initial reactive state of the store | 
actions | Contains methods to modify state and handle business logic. | 
getters | Computed functions that derive values from the state | 
Let’s look at each of them in detail,
State
The state contains the variables and data managed by the store. The state in Pinia is automatically reactive, meaning any changes in the state will be immediately reflected in the components that use it.
userStore.setCurrentUser({ id: 1, name: 'John Doe' });For example, if we update currentUser in the store, any component that depends on currentUser will automatically update:
Actions
Actions handle complex logic or API calls.
actions: {
  async fetchUsers() {
    try {
      const response = await fetch('/api/users');
      this.users = await response.json();
    } catch (error) {
      console.error('Error fetching users:', error);
    }
  },
},Getters
Getters are like computed() but at the store level.
// stores/counter.js
getters: {
  multiplyBy: (state) => (factor) => state.count * factor,
}Like computed properties, getter methods are also cached.
Using the Store in Components
To use the store in a component, we simply import and call the store:
<script setup>
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
// Access the state
console.log(userStore.users);
// Call an action
userStore.fetchUsers();
// Use a getter
console.log(userStore.activeUsers);
</script>Modifying the State
Logically, we will want to modify the state of the store. In Pinia, we have several ways to do this.
The recommended way to modify the state is through actions:
const counter = useCounterStore()
counter.increment()In Pinia, we can modify the state directly (without methods or mutations).
counterStore.count++; // ✅ Works (reactivity maintained)We can also reset the state of a state variable,
counterStore.$reset(); // Goes back to the initial stateTo update multiple properties at once, you can use $patch:
// Passing an object
counter.$patch({
  count: counter.count + 1,
  name: 'New name'
})
// Passing a function (more efficient for changes in arrays)
counter.$patch((state) => {
  state.count++
  state.items.push({ id: state.nextId++, text: 'New item' })
})Reactivity to State
Now we will want our App to react to changes in the store. That is, we will want to maintain reactivity in our components.
Pinia provides a helper called storeToRefs that automatically converts all properties of the state and the getters into reactive refs.
<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';
const userStore = useUserStore();
const { users, currentUser, activeUsers } = storeToRefs(userStore);
// Actions are used directly from the store
userStore.fetchUsers();
</script>users,currentUser, andactiveUsersare nowref()reactive.fetchUsersand other actions are kept out ofstoreToRefsbecause they are functions and do not need to be reactive.
Destructuring directly from the store would break reactivity. That is, if you do this,
const { users, currentUser } = userStore; // ⚠️ This breaks reactivityYou will break reactivity. That’s why we used storeToRefs,
Subscriptions to State Changes
Pinia also allows subscribing to changes in the store’s state. Under the hood, it is equivalent to using a watch.
const unsubscribe = counter.$subscribe((mutation, state) => {
  // Executed when the state changes
  console.log('Updated state:', state)
  console.log('Mutation:', mutation)
  
  // Optionally, you can save the state to localStorage
  localStorage.setItem('counter', JSON.stringify(state))
})
// Later you can unsubscribe
unsubscribe()Using Multiple Stores
In large applications, it is common to split the state into multiple stores. Pinia makes this organization easy by allowing each store to be independent.
For example, we could have one store for users, another for products, and another for application settings.
// stores/
export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
  }),
});export const useProductStore = defineStore('product', {
  state: () => ({
    products: [],
  }),
});