If you've worked with Vue 2, you've probably used Vuex: Vue's official solution for state management.
Now, with Vue 3 and its Composition API, we don't have a new version of Vuex but its replacement: Pinia. In this DevTip, you'll learn to take the first steps with Pinia.
But what exactly is Pinia?
What is Pinia?
Pinia removes much of the complexity of Vuex in several areas:
- There are no modules as such, but different stores (like the leaves of a pineapple 🍍) that can easily communicate.
- There are no mutations because they are not necessary to track state changes (with the exposed Vue 3 reactivity API,
we can mutate
state
from anywhere). - It's much easier to import our stores into any component or composable.
- Better (default) TypeScript support.
- Native support for Vue DevTools 🔥.
- Extremely lightweight: around 1.5kb.
I suggest you take a look at the official Pinia documentation.
Using Vue 3 and Pinia
Installing Pinia
First, of course, install Pinia and add it to our setup.
So yarn add pinia
and then:
// main.js
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
Creating a store with Pinia
Now we can create our store, for example, in stores/counter.js
. Here we will use the Pinia defineStore
method to
create a new store with at least the options
property for the state, an increment
action to mutate it, and
a totalClicks
getter that will return a computed value.
// stores/counter.js
import {defineStore} from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
options: {
a: 0,
b: 0,
c: 0,
},
}),
actions: {
increment(option) {
this.options[option]++;
},
},
getters: {
totalClicks() {
return Object.values(this.options).reduce((total, current) => {
return total + current;
}, 0);
},
},
});
Using a store created with Pinia
To use a store created with Pinia, we just have to import it into any component
or Vue 3 composable, so let's import useCounterStore
and then execute the function that will return the store.
We save all of this in the store
constant.
<!-- Component.vue -->
<script setup>
import {useCounterStore} from '../stores/counter';
const store = useCounterStore();
</script>
Now we can use its properties: state, actions, and getters. By using the script setup
syntax, we have direct access to
all of them in the template.
Notice how we interpolate the reactive getter totalClicks
, display the value of each options
property
with store.options[option]
, and call the store.increment[option]
action as if it were another method (which it is).
<!-- Component.vue -->
<template>
<h2>Total Clics: {{ store.totalClicks }}</h2>
<div class="card">
<button type="button" @click="store.increment('a')">
Option A clicked {{ store.options['a'] }} times
</button>
<button type="button" @click="store.delayedIncrement('b')">
Option B clicked {{ store.options['b'] }} times
</button>
<button type="button" @click="store.increment('c')">
Option C clicked {{ store.options['c'] }} times
</button>
</div>
</template>
Asynchronous Actions with Pinia
Just like with Vuex, Pinia actions can also be asynchronous. Imagine a case where you make an HTTP request and have to wait for the response to update the state or call other actions.
Let's create delayedIncrement
to wait for a number of milliseconds before updating options
.
As simple as this:
// stores/counter.js
const wait = (time = 1000) =>
new Promise((resolve) => setTimeout(resolve, time));
export const useCounterStore = defineStore('counter', {
...
actions
:
{
...
async
delayedIncrement(option)
{
await wait();
this.options[option]++;
}
,
}
,
...
})
;
Destructuring Pinia with storeToRefs
Just like with the Vue 3 reactive
API, we cannot
destructure the resulting values when instantiating our store. This won't work:
const store = useCounterStore();
const {options, increment, totalClicks} = useCounterStore(); // ❌
To avoid losing reactivity in your store, use the Pinia helper storeToRefs
:
import {storeToRefs} from 'pinia'
const store = useCounterStore();
const {options, increment, totalClicks} = storeToRefs(store); // ✅
Conclusion
- You can see how easy it is to take the first steps with Pinia and Vue 3.
- Happy coding!🔥