State Management in Vue with Pinia
Why Pinia replaced Vuex as Vue's recommended state management library, and how to set up stores in both Options and Composition API styles.
Vue 3's Composition API changed how you organize component logic — instead of scattering related state across data, computed, and methods, you group everything that belongs together in one place. Here's a practical introduction for developers coming from the Options API or another framework entirely.
In a large Options API component, all the logic for one feature (say, a search filter) ends up spread across three separate blocks — some state in data(), a computed property in computed, a handler in methods. The Composition API lets you write all of that together as one cohesive, reusable function.
<script setup>
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
<template>
<button @click="increment">Count: {{ count }} (doubled: {{ doubled }})</button>
</template>
<script setup> is the modern, recommended syntax — everything declared at the top level is automatically exposed to the template, no explicit return statement needed.
const count = ref(0); // primitive — access/mutate via .value
const user = reactive({ name: 'Bikesh', role: 'Developer' }); // object — no .value needed
ref works for any value type (primitives included) and needs .value in JavaScript, but not in the template — Vue unwraps it automatically there. reactive only works on objects/arrays and never needs .value, but loses reactivity if you destructure it.
The real payoff of the Composition API is extracting logic into reusable "composables" — plain functions that use Vue's reactivity APIs:
// useCounter.js
import { ref } from 'vue';
export function useCounter(initial = 0) {
const count = ref(initial);
const increment = () => count.value++;
const decrement = () => count.value--;
return { count, increment, decrement };
}
<script setup>
import { useCounter } from './useCounter';
const { count, increment } = useCounter(10);
</script>
Any component can now share this exact logic — something the Options API had no clean equivalent for beyond mixins, which came with their own naming-collision problems.
import { watch } from 'vue';
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`);
});
The Composition API isn't a replacement that makes the Options API wrong — it's a better tool for components whose logic has grown past a handful of lines, which in a real app is most of them.
Why Pinia replaced Vuex as Vue's recommended state management library, and how to set up stores in both Options and Composition API styles.
How Inertia.js lets a Laravel backend and Vue.js frontend feel like one SPA-style codebase, without building a separate JSON API.