Home About Skills Products Work
Projects Services Experience
Learn
Tutorials Courses Blogs Resources
Contact
Blog · Vue.js

State Management in Vue with Pinia

State Management in Vue with Pinia

Vuex was Vue's original state management library, but Pinia has replaced it as the officially recommended choice — simpler API, full TypeScript support, and no more mutation boilerplate. Here's how to actually use it.

Why Pinia Over Vuex

Vuex required a strict state/getters/mutations/actions split, and mutations existed purely to satisfy Vue DevTools — you couldn't change state directly even when it would've been perfectly safe. Pinia drops mutations entirely: actions can modify state directly, and it still fully supports time-travel debugging.

Installing and Creating a Store

npm install pinia
// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubled: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++; // direct mutation — no separate mutation function needed
    },
  },
});

Using a Store in a Component

<script setup>
import { useCounterStore } from '@/stores/counter';
const counter = useCounterStore();
</script>

<template>
  <button @click="counter.increment()">{{ counter.count }} ({{ counter.doubled }})</button>
</template>

No mapState/mapActions helpers needed — the store's properties and actions are just plain reactive object properties, destructurable with storeToRefs when you need to keep reactivity after destructuring.

The Composition API Style Store

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);
  const doubled = computed(() => count.value * 2);
  function increment() { count.value++; }
  return { count, doubled, increment };
});

This mirrors a component's own <script setup> structure exactly — if Composition API syntax already feels natural, this store style will too.

Persisting State

Pinia has no built-in persistence, but the community pinia-plugin-persistedstate package handles localStorage syncing in one line of config — essential for things like a shopping cart that should survive a page refresh.

When You Actually Need a Store

Not every piece of state belongs in Pinia. Component-local UI state (a form's current input, a dropdown's open/closed state) should stay in that component. Reach for a store specifically for state that's genuinely shared across multiple, unrelated parts of the app — auth status, a shopping cart, user preferences.

Vue 3 Composition API: A Practical Introduction

Vue 3 Composition API: A Practical Introduction

setup(), ref vs reactive, and reusable composables — a practical introduction to Vue 3's Composition API for developers coming from Options API or another framework.

Building a Full-Stack App with Vue.js and Laravel (Inertia.js)

Building a Full-Stack App with Vue.js and Laravel (Inertia.js)

How Inertia.js lets a Laravel backend and Vue.js frontend feel like one SPA-style codebase, without building a separate JSON API.

Esc