Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Pinia: A Modern, Simpler State Manager for Vue 3

Tech May 19 1

Download and install Pinia first:

npm install pinia --save

In Vue 3's main.js, initialize Pinia:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import RootApp from './RootApp.vue'

const storeInstance = createPinia()
const vueApp = createApp(RootApp)

vueApp.use(storeInstance)
vueApp.mount('#app')

Pinia replaces Vuex for Vue 3 primarily due to these advantages:

  • No redundant mutations; devtools integration works without them.
  • TypeScript support is built-in with reliable type inference.
  • Flat store architecture eliminates nested modules, while allowing implicit store composition via imports.
  • Dynamic store registration is automatic, eliminating manual setup.
  • API follows Vue 3's Composable API style, using direct function calls and auto-completion.

State Management

Direct State Modification and Batch Updates

Pinia allows direct state mutation:

const catalogStore = useProductCatalog()
catalogStore.productCount++

For batch updates, use $patch with an object or function:

// Object patch
catalogStore.$patch({ productCount: 10, isUpdated: true })

// Function patch
catalogStore.$patch(state => {
  state.products.push({ name: 'Wireless Headphones', price: 99.99 })
  state.isUpdated = true
})

State Subscription

Use $subscribe() to watch state changes; it triggers only once per patch (unlike watch):

catalogStore.$subscribe((mutation, state) => {
  mutation.type // 'direct' | 'patch object' | 'patch function'
  mutation.storeId // 'product-catalog'
  mutation.payload // Patch object (only for 'patch object' type)

  localStorage.setItem('productCatalog', JSON.stringify(state))
})

In Composable API, keep subscriptions active after component unmount:

<script setup>
import { useProductCatalog } from './stores/product-catalog'
const catalogStore = useProductCatalog()
catalogStore.$subscribe((mutation, state) => {
  console.log('State updated:', state)
}, { detached: true })
</script>

Getters

Accessing Other Store Getters

Import and use other stores directly within a getter:

import { useUserProfile } from './user-profile'

export const useProductCatalog = defineStore('product-catalog', {
  state: () => ({ products: [] }),
  getters: {
    availableProducts(state) {
      const userStore = useUserProfile()
      return state.products.filter(p => p.price <= userStore.maxBudget)
    }
  }
})

Passing Parameters to Getters

Return a function from a getter to accept parameters (note: this disables caching):

export const useProductCatalog = defineStore('product-catalog', {
  state: () => ({ products: [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Phone', price: 699 }
  ] }),
  getters: {
    getProductById: (state) => (productId) => 
      state.products.find(p => p.id === productId)
  }
})

Component usage:

<script setup>
import { useProductCatalog } from './stores/product-catalog'
const catalogStore = useProductCatalog()
const product = catalogStore.getProductById(1)
</script>

<template>
  <p>Product: {{ product?.name }}</p>
</template>

Actions

Accessing Other Store Actions

Import and call other store actions within an action:

import { useAuthSession } from './auth-session'

export const useOrderManager = defineStore('order-manager', {
  state: () => ({ orderDetails: null }),
  actions: {
    async fetchUserOrders() {
      const authStore = useAuthSession()
      if (authStore.isLoggedIn) {
        this.orderDetails = await fetch('/api/orders')
      } else {
        throw new Error('User not authenticated')
      }
    }
  }
})

Action Subscriptions

Use $onAction() to listen to action calls, results, and errors:

const orderStore = useOrderManager()
const unsubscribe = orderStore.$onAction(({
  name, store, args, after, onError
}) => {
  const startTime = Date.now()
  console.log(`Starting "${name}" with params: [${args.join(', ')}]`)

  after(result => {
    console.log(`"${name}" completed in ${Date.now() - startTime}ms
Result: ${result}`)
  })

  onError(error => {
    console.warn(`"${name}" failed in ${Date.now() - startTime}ms
Error: ${error}`)
  })
})

unsubscribe() // Manual cleanup

For persistent subscriptions (even after component unmount):

<script setup>
import { useOrderManager } from './stores/order-manager'
const orderStore = useOrderManager()
orderStore.$onAction(callback, true)
</script>

Plugins

Adding Global Store Properties

Create plugins to add shared properties to all stores:

import { createPinia } from 'pinia'

function GlobalStorePlugin() {
  return { appVersion: '1.0.0', apiBaseUrl: '/api' }
}

const storeInstance = createPinia()
storeInstance.use(GlobalStorePlugin)

// Usage in another file
const anyStore = useAnyStore()
console.log(anyStore.appVersion) // '1.0.0'

Adding Per-Store and Shared Refs

Use ref() to add reactive properties; per-store properties are unique, shared refs are identical across stores:

import { ref } from 'vue'

const sharedRef = ref('Shared Value')
storeInstance.use(({ store }) => {
  store.localProperty = ref('Unique Per Store')
  store.sharedProperty = sharedRef
})

Adding Custom Store Options

Define new options when creating stores and use plugins to process them. For example, add a throttle option to limit action frequency:

// In store definition
import { defineStore } from 'pinia'

export const useProductCatalog = defineStore('product-catalog', () => {
  // Setup store logic
  return { products: [] }
}, {
  throttle: { fetchProducts: 500 } // Throttle fetchProducts to 500ms
})

// Plugin implementation
import throttle from 'lodash/throttle'

storeInstance.use(({ options, store }) => {
  if (options.throttle) {
    return Object.keys(options.throttle).reduce((throttledActions, actionName) => {
      throttledActions[actionName] = throttle(
        store[actionName],
        options.throttle[actionName]
      )
      return throttledActions
    }, {})
  }
})

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.