Tutorials Logic, IN info@tutorialslogic.com

Pinia Vue State Management

Shared Application State

Pinia stores shared state and domain actions outside the component tree while integrating with Vue reactivity and developer tools. A store has a unique ID and exposes state, getters, and actions. Components should still keep temporary presentation state locally; a store is for data and behavior that multiple parts of the application coordinate.

After this lesson, you can define a store, consume its state without breaking reactivity, keep asynchronous writes in actions, and choose between local component state, a composable, and Pinia.

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)

Pinia - Store Definition and Usage

Pinia - Store Definition and Usage
// 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()
        }
    }
})

What is Pinia? - JavaScript Example

What is Pinia? - JavaScript Example
// 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 }
})

What is Pinia? - HTML Example

What is Pinia? - HTML Example
<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>

Store Design

Model a store around a domain capability such as cart, session, or catalog rather than a page name. State holds the source values, getters derive values such as a total, and actions perform transitions such as addItem or checkout. Keep mutation rules inside actions when several components must follow the same policy.

A setup store uses refs for state, computed values for getters, and functions for actions. Return every state property that Pinia must track. An option store declares state as a function, which gives each application instance its own initial state and supports predictable reset behavior.

Reactive Consumption

Do not destructure reactive state directly from the store object into ordinary variables. Use storeToRefs for state and getters; actions can be destructured normally because they are bound methods. This distinction prevents a component from displaying the initial value forever while the store changes elsewhere.

Actions may be asynchronous and can call other actions. Represent loading and failure deliberately when several components depend on the request. Prevent duplicate submissions at the action boundary and avoid leaving partially updated state when a request fails.

Lifecycle and Reset

Decide when store data becomes stale and when it should reset. User-specific state should be cleared on logout. Route changes do not automatically destroy a store, which is useful for shared state but surprising when a developer expects page-local cleanup. Persist only the fields that genuinely need persistence and never put secrets in browser storage.

Before you move on

Store Review

4 checks
  • Use one stable store ID and a domain-focused name.
  • Keep source values in state and derived values in getters.
  • Use storeToRefs when destructuring state or getters.
  • Reset user-specific state when the session ends.

Store State Bugs

  • Putting every local toggle in Pinia.

    Keep state local unless multiple owners need coordination.
  • Destructuring state directly from a store.

    Use storeToRefs to retain reactivity.
  • Persisting authentication secrets in a plugin.

    Use secure server-managed credentials and persist only non-sensitive preferences.

Try this next

Build a Cart Store

0 of 2 completed

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.