aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Development

Vue Pinia for State Management: Moving On From Vuex

vue pinia is the official, and these days recommended, way to manage shared state in Vue 3 applications. When several components need the same data — the logged-in user, the shopping cart, the theme preference — you want to keep it in one central place. The old standard for this was Vuex, but the Vue team now recommends Pinia for new projects. In this article we'll see, step by step, how to build a store from scratch with Pinia instead of Vuex.

Why Pinia instead of Vuex?

Pinia is the official state-management library that replaces Vuex, and it's preferred because it solves a few concrete problems:

  • Less ceremony: in Vuex you had to write mutations to change anything. Pinia drops the mutation concept entirely; you change state directly or from inside actions.
  • Full TypeScript support: types are inferred automatically; this and return values are typed without extra boilerplate.
  • No modules, just flat stores: instead of Vuex's nested module structure, every store is an independent unit. You call any store from anywhere.
  • Built for the Composition API: Pinia works naturally with Vue 3's setup logic and ships with devtools support.

In short, Pinia does everything Vuex did with less code and better type safety. If you already have a Vuex project you don't have to migrate by force, but writing new stores in Pinia makes sense.

Installing and registering Pinia

First install the package, then create an instance with createPinia() and register it on your app:

npm install pinia
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
app.use(createPinia());
app.mount("#app");

That's all. You can now define and use stores anywhere in the app. Instead of Vuex's single giant store object, with Pinia you create several small stores.

Your first store: state, getters and actions

A store is defined with defineStore. The first argument is a unique id (devtools uses it), the second is the store's contents. Here is the classic counter example:

// stores/counter.js
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", {
  state: () => ({
    count: 0,
    name: "Counter",
  }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
    reset() {
      this.count = 0;
    },
  },
});

You can see the three core pieces:

  • state: a function that returns the initial data. Being a function ensures each instance gets its own fresh state.
  • getters: computed values derived from state. They are cached like Vue's computed; here double won't recompute unless count changes.
  • actions: methods that change state and hold your business logic. Unlike Vuex there is no sync/async split; an action can be async directly.

Notice there is no mutations section like in Vuex. When you want to change state you simply write this.count++ inside an action.

Using the store in a component

To use a store you call it inside setup. The returned object is reactive, so you can use it straight in the template:

<script setup>
import { useCounterStore } from "@/stores/counter";

const counter = useCounterStore();
</script>

<template>
  <p>Count: {{ counter.count }}</p>
  <p>Double: {{ counter.double }}</p>
  <button @click="counter.increment()">Increment</button>
  <button @click="counter.reset()">Reset</button>
</template>

Here counter.count, counter.double and counter.increment() are directly accessible. You read state in the template and trigger an action with a click.

storeToRefs: destructuring without losing reactivity

Often you want to pull a few fields out of the store and use them with short names. But if you destructure the store directly, reactivity is lost:

// WRONG — count is no longer reactive
const { count, double } = useCounterStore();

The correct way is to use storeToRefs for state and getters. This helper returns each field as a reactive ref. Actions, being functions, can be destructured directly:

import { storeToRefs } from "pinia";

const store = useCounterStore();
const { count, double } = storeToRefs(store); // stays reactive
const { increment, reset } = store;           // actions taken as-is

Async actions and batch updates

In real apps, actions frequently make API calls. Unlike Vuex you don't need a separate construct; just make the action async:

actions: {
  async fetchUser(id) {
    this.loading = true;
    try {
      const res = await fetch(`/api/users/${id}`);
      this.user = await res.json();
    } finally {
      this.loading = false;
    }
  },
}

If you want to update several fields at once you can use $patch; this is tracked as a single change and looks cleaner in devtools:

store.$patch({ count: 10, name: "New" });

You can also reset state to its initial value with store.$reset(), and listen to every change with store.$subscribe() (for example to write to localStorage). These are small but valuable details that show how much Pinia simplifies day-to-day work compared with Vuex.

Frequently Asked Questions

Should I use Pinia or Vuex?

Pinia for new Vue 3 projects. Vue's official recommendation is now Pinia, and Vuex is in maintenance mode. If you have a large existing Vuex project, urgent migration isn't required, but writing new features in Pinia is the most future-proof choice.

Why are there no mutations in Pinia?

Vuex required mutations so that devtools could track changes. Because Pinia can track state changes directly, that intermediate layer became unnecessary; you change state inside an action or with $patch, and devtools still records every step.

Can multiple stores talk to each other?

Yes. Inside one store's action or getter you can call another store: you invoke the relevant useOtherStore() function and use the returned instance. Stores can reach each other without Vuex's module namespace complexity.

A well-structured state architecture is the backbone of a growing Vue app. If you're building a Vue 3 + Pinia project or want to modernize existing Vuex code, get in touch with me — let's set up a clean, scalable structure together.

Bu kategorideki tüm yazılar →

Devamı için