State Management with Pinia

Dikih Arif Wibowo
4 min readJan 19, 2023
Photo by Tudor Baciu on Unsplash

State management library is a library used to manage state in a JavaScript application. Managing the relationship between state and component in Vue can use props & $emit. But in applications that have multiple components managing state can be tricky.

A state can be used by many components, often the state must be moved to the parent component (lifting up) so that the state can be used by other components.

When using state management we no longer need to move state from one component to another. Because state management converts state to global state and places it in a place called store. Each component can use the state in the store directly. And one of the state management that we can use in Vue application is Pinia.

Introduction Pinia

Pinia is a lightweight state management library for Vue.js. As a frontend engineer Pinia deserves us to try and learn. This is because if you use vuex for state management, Pinia is de facto vuex 5. Yup, that’s what Evan Yousaid on his Twitter.

what’s great about Pinia, we don’t need mutations to change the state. in Vuex we need actions & mutations to change state. let’s see the difference.

//Vuex Way
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})

In pinia it’s simpler, we only need actions to change the state.

//Pinia Way
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
actions: {
increment() {
this.count++;
}
}
})

let’s learn to use pinia for state management. I’ve made a simple counter app that you can access here. And you can access the code simple counter app on my github. we will use a case study of create counter app using Pinia.

Store

use the defineStore() function to define the store. In defineStore() we have to give a unique name to the first argument.

// stores/counter.jsimport { defineStore } from "pinia";export const useCounterStore = defineStore('counter', {})

to access the store using the setup script, it’s very simple.

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

State

To create state is very easy. If you are using Vue 2, the data you create in state follows the same rules as the data in a Vue instance

export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
}

})

while to access the state we simply import the store.

<script setup>
import { computed } from 'vue'
import { useCounterStore } from '../../stores/counter'
const counterStore = useCounterStore()
const count = computed(() => counterStore.count)
</script>

Mutations state

There are several ways to mutate the state. we can directly mutate the store with store.counter++ or you can use $patch method. By using $patch it allows you to apply multiple changes at the same time with a partial state object:

store.$patch({
counter: store.counter + 1,
name: 'john doe',
})

But this method is not recommended, we can use actions to mutate the state.

in pinia we can also reset state. the method is also quite easy, we can use the $reset()method.

export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
actions: {
reset() {
// this.count = 0;
this.$reset();
}
}
})

Subscribing state

with $subscribe() you can watch the state and its changes through the $subscribe() method of a store.

in the context of the counter app, say we want to reset the store if the counter value is more than 5.

<script setup>
import { useCounterStore } from '../../stores/counter'
const counterStore = useCounterStore()counterStore.$subscribe((mutation, state) => {
if(mutation.storeId === 'counter' && state.count > 5) {
counterStore.reset()
}
})

</script>

Actions

Actions are the equivalent of methods in components. They can be defined with the actions property in defineStore() , actions are used for state mutations (change state).

export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
actions: {
increment() {
this.count++;
},
decrement() {
if(this.count > 0) {
this.count--;
}
},
reset() {
// this.count = 0;
this.$reset();
}
}

})

To access the action is almost the same as accessing the state.

<script setup>
import { useCounterStore } from '../../stores/counter'
const counterStore = useCounterStore() // change the state of the store with actions
const increment = () => {
counterStore.increment()
}
const decrement = () => {
counterStore.decrement()
}
const reset = () => {
counterStore.reset()
}
</script>

Getters

Getters are exactly the equivalent of computed values for the state of a Store. They can be defined with the getters property in defineStore(). They receive the state as the first parameter to encourage the usage of arrow function:

export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
getters: {
doubleCount: state => state.count * 2
},

})

to access getters, here’s an example.

<script setup>
import { useCounterStore } from '../../stores/counter'
const counterStore = useCounterStore()
const doubleCount = counterStore.doubleCount
</script>

--

--