Skip to content

Pinia: Next-Generation Vue State Management

Pinia is Vue's official state management library, designed to be type-safe, modular, and very friendly to the Composition API. Compared to Vuex, Pinia's API is more concise, accessing the store in templates is more direct, and it provides excellent TypeScript support.

Why Choose Pinia?

  • Simpler API: No mutations, only state, getters, and actions. Actions can be synchronous or asynchronous.
  • Complete TypeScript Support: Get perfect type inference without creating complex type declarations.
  • Modular Design: Each store is an independent module that can be imported and used on demand.
  • Very Lightweight: Very small size, about 1KB.
  • Plugin Support: Pinia's functionality can be extended through plugins.

Installation and Configuration

First, install Pinia:

bash
npm install pinia

Then, create and use a Pinia instance in your main.js file:

src/main.js

js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)

app.use(createPinia()) // Create and use Pinia instance
app.mount('#app')

Defining a Store

In Pinia, a store is defined through defineStore(). By convention, we usually create store files in the src/stores/ directory.

src/stores/counter.js

js
import { defineStore } from 'pinia'

// The first parameter is the store's unique ID
export const useCounterStore = defineStore('counter', {
  // state: returns a function to prevent data state pollution caused by cross-requests during server-side rendering
  state: () => ({
    count: 0,
    name: 'Eduardo'
  }),
  // getters: similar to component's computed properties
  getters: {
    doubleCount: (state) => state.count * 2,
    doubleCountPlusOne() {
      // Can access other getters via `this`
      return this.doubleCount + 1
    }
  },
  // actions: similar to component's methods, can be asynchronous
  actions: {
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    }
  }
})

Using the Store in Components

In the component's <script setup>, you can directly import and call the store's hook function to get the store instance.

CounterComponent.vue

vue
<script setup>
import { useCounterStore } from '@/stores/counter'

// Get the store instance
const counter = useCounterStore()

// Can directly use the store's state, getters, and actions in the template
// counter.count
// counter.doubleCount
// counter.increment()
</script>

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
  </div>
</template>

Destructuring the Store

If you directly destructure from a Pinia store, you'll lose its reactivity. To solve this problem, you can use storeToRefs.

vue
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// `storeToRefs` converts state and getters into reactive refs
const { count, doubleCount } = storeToRefs(counter)
const { increment } = counter // actions can be destructured directly
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

Pinia is rapidly becoming the preferred solution for state management in Vue 3 projects due to its concise and powerful features.

Content is for learning and research only.