# Frontend state management: Pinia in the post-Vuex era

> How Pinia replaced Vuex in the Vue ecosystem: less boilerplate, better TypeScript support, and a more mature approach to state management.

- Published: 2023-06-04
- Category: Interface
- Tags: Vue.js
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/frontend-state-management-pinia-after-vuex/
- Language: en-US
- Author: Muhammet Şafak

---
In the Vue ecosystem, [**state management**](/en/blog/state-management-in-vue-with-vuex/) was Vuex's domain for a long time. Vuex got the job done — but when [Vue 3 and the Composition API](/en/blog/vue-3-composition-api-why-and-when/) arrived, some rough edges became more visible. There was too much boilerplate, TypeScript support had been bolted on after the fact, and organizing a store around the mutation-action-getter triple often made simple problems feel unnecessarily complex.

Pinia emerged from Vue's own team as the official successor to Vuex. It was designed for Vue 3, built to work naturally with the Composition API, and shipped with first-class TypeScript support from day one. Vuex is now in maintenance mode; Pinia is the officially recommended path right now.

## Writing a store with Pinia

The most striking difference with Pinia is just how concise a store definition looks. Instead of the four-layer `state`/`mutations`/`actions`/`getters` structure from Vuex, you have a single `defineStore` call:

```javascript
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
    const items = ref([])

    const totalPrice = computed(() =>
        items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
    )

    function addItem(product) {
        const existing = items.value.find(i => i.id === product.id)
        if (existing) {
            existing.quantity++
        } else {
            items.value.push({ ...product, quantity: 1 })
        }
    }

    function removeItem(productId) {
        items.value = items.value.filter(i => i.id !== productId)
    }

    return { items, totalPrice, addItem, removeItem }
})
```

This is a store written exactly like a Composition API setup function. `ref` and `computed` are things you already know — there is no separate API to learn.

Using it in a component:

```javascript
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()

// Direct access
console.log(cart.totalPrice)
cart.addItem(product)
```

## Comparison with Vuex

Doing the same thing in Vuex required at least four layers: defining state, writing a mutation, writing an action, defining a getter. Then in the component you'd wire everything up with `mapState`, `mapActions`, and `mapGetters`.

Pinia reduces this to three: state (`ref`), computed, and function. And all three are the standard building blocks of Vue's Composition API.

On the TypeScript front, Vuex couldn't infer types for accesses like `$store.state.cart.items` — manual type declarations were required. With Pinia, the moment you define your store with `defineStore`, every type is inferred automatically. The object returned by `useCartStore()` is fully typed.

## Multiple stores

Cross-module access in Vuex could get confusing at times: string-based dispatches like `this.$store.dispatch('auth/logout')`. In Pinia, stores are independent of each other; if you want to use one store inside another, you simply import it.

```javascript
// stores/order.js
import { useCartStore } from './cart'
import { useAuthStore } from './auth'

export const useOrderStore = defineStore('order', () => {
    async function placeOrder() {
        const cart = useCartStore()
        const auth = useAuthStore()

        // cart.items and auth.user are directly accessible
        // ...
    }

    return { placeOrder }
})
```

No string references, no module hierarchy, just plain JavaScript.

## $reset() and resetting store state

A feature that was missing in Vuex is available as an option in Pinia: `$reset()`. In stores written with the Options API style, it resets state back to its initial values. It comes in handy when logging out (clearing all stores) or cleaning up between steps in a form wizard.

When you use the setup style (the `() => {}` form I showed above), `$reset()` does not come out of the box — you have to write your own reset function. That is a small trade-off: you gain the flexibility of setup style, but give up the convenience of automatic `$reset()`.

## When you do not need state management

No matter how lean Pinia is, state management is not the right tool for every problem. If only a single component uses a piece of data, that component's own `ref`s are sufficient. If data is shared between two components, a `props`-and-`emit` cycle is usually cleaner.

A store makes the most sense in one specific scenario: when data needs to be read or mutated by multiple components that have no direct relationship with each other. A shopping cart, authentication state, global notifications — those are good candidates. The form state of a single page component — probably not.

## Where I stand with Pinia

Migrating to Pinia turned out to be one of the smoothest transitions developers went through with Vue 3. The learning curve is nearly flat; if you know the Composition API, you understand Pinia at first glance. The reduction in boilerplate is substantial — especially noticeable in mid-sized stores.

It is encouraging to see the Vue ecosystem maturing. Vuex was not wrong; it was the right solution for its time. Pinia fits more naturally into Vue's current philosophy.
