State Management Documentation: Vue 3 + Pinia 

What is State Management? 

State management is the systematic handling of application data across components. In frontend applications, it ensures consistent data flow, predictable updates, and maintainable code structure. 

Why Pinia? 

Pinia is the official state management library for Vue 3, replacing Vuex. It offers: 

  • TypeScript support out of the box 
  • Composition API syntax 
  • DevTools integration 
  • Modular architecture 
  • Less boilerplate code 

1. Store Definition 

A store is a reactive container holding state and business logic. 

Basic Store Structure: 

javascript 

// stores/counter.js 
import { defineStore } from ‘pinia’ 
 
export const useCounterStore = defineStore(‘counter’, () => { 
  // State 
  const count = ref(0) 
   
  // Getters (Computed) 
  const doubleCount = computed(() => count.value * 2) 
   
  // Actions (Methods) 
  function increment() { 
    count.value++ 
  } 
   
  function reset() { 
    count.value = 0 
  } 
   
  return { count, doubleCount, increment, reset } 
}) 

2. State 

Reactive data that represents your application’s condition. 

State Patterns: 

  • Use ref() for primitive values 
  • Use reactive() for objects 
  • Initialize with default values 
  • Keep state serializable when possible 

3. Getters 

Computed values derived from state. 

Getter Characteristics: 

  • Always return a value 
  • Can accept arguments 
  • Cached based on dependencies 
  • Use other getters 

4. Actions 

Methods that modify state or perform side effects. 

Action Guidelines: 

  • Can be async/await 
  • Can call other actions 
  • Should contain business logic 
  • Can call APIs or other stores 

Store Patterns 

Basic Store 

javascript 

// stores/user.js 
export const useUserStore = defineStore(‘user’, () => { 
  const user = ref(null) 
  const isLoggedIn = computed(() => !!user.value) 
   
  async function login(credentials) { 
    const response = await api.login(credentials) 
    user.value = response.data 
  } 
   
  function logout() { 
    user.value = null 
  } 
   
  return { user, isLoggedIn, login, logout } 
}) 

Store with Persistence 

javascript 

// stores/auth.js 
export const useAuthStore = defineStore(‘auth’, () => { 
  const token = ref(localStorage.getItem(‘token’)) 
  const user = ref(JSON.parse(localStorage.getItem(‘user’) || ‘null’)) 
   
  // Auto-persist to localStorage 
  watch(token, (newToken) => { 
    if (newToken) localStorage.setItem(‘token’, newToken) 
    else localStorage.removeItem(‘token’) 
  }) 
   
  watch(user, (newUser) => { 
    if (newUser) localStorage.setItem(‘user’, JSON.stringify(newUser)) 
    else localStorage.removeItem(‘user’) 
  }) 
   
  return { token, user } 
}) 

Component Usage 

Accessing Stores in Components 

vue 

<template> 
  <div> 
    <p>Count: {{ counter.count }}</p> 
    <p>Double: {{ counter.doubleCount }}</p> 
    <button @click=”counter.increment”>Increment</button> 
    <button @click=”counter.reset”>Reset</button> 
  </div> 
</template> 
 
<script setup> 
import { useCounterStore } from ‘~/stores/counter’ 
 
const counter = useCounterStore() 
</script> 

Store Destructuring 

vue 

<script setup> 
import { useCounterStore } from ‘~/stores/counter’ 
import { storeToRefs } from ‘pinia’ 
 
const counter = useCounterStore() 
 
// Proper destructuring with reactivity 
const { count, doubleCount } = storeToRefs(counter) 
const { increment, reset } = counter 
</script> 

Advanced Patterns 

Cross-Store Actions 

javascript 

// stores/cart.js 
export const useCartStore = defineStore(‘cart’, () => { 
  const items = ref([]) 
  const userStore = useUserStore() 
   
  async function checkout() { 
    if (!userStore.isLoggedIn) { 
      throw new Error(‘Must be logged in to checkout’) 
    } 
     
    // Use user data from another store 
    const order = await api.createOrder({ 
      userId: userStore.user.id, 
      items: items.value 
    }) 
     
    items.value = [] 
    return order 
  } 
   
  return { items, checkout } 
}) 

Async Actions with Loading States 

javascript 

// stores/products.js 
export const useProductsStore = defineStore(‘products’, () => { 
  const products = ref([]) 
  const loading = ref(false) 
  const error = ref(null) 
   
  async function fetchProducts() { 
    loading.value = true 
    error.value = null 
     
    try { 
      const response = await api.getProducts() 
      products.value = response.data 
    } catch (err) { 
      error.value = err.message 
    } finally { 
      loading.value = false 
    } 
  } 
   
  return { products, loading, error, fetchProducts } 
})