January 4, 2023 ยท Nuxt 3
Getting started with Pinia for global state management.
In this post, we'll introduce Pinia, a powerful package for managing your Nuxt app's state in a single place.
Whether you're new to state management solutions or experienced with libraries such as Vuex and Redux, Pinia is definitely worth checking out.
If you've ever found yourself aimlessly trying to manage state through props and events, then the idea of a store may sound appealing:
This helps to make changes to the app's state predictable and more consistent.
For example, we can store a counter, and then increment it from anywhere by using its store:
Pinia is a state management library for Vue, with an officially-supported module for Nuxt 3 (@pinia/nuxt
). It's also the recommended solution for Vue and Nuxt projects.
Don't just take it from me:
"Pinia is de facto Vuex 5!"
โ Evan You, creator of Vue (source)
What makes it useful for Vue and Nuxt applications?
methods
)computed
)Official documentation for using Pinia with Nuxt can be found here.
Install the package:
yarn add @pinia/nuxt
Add the module to your Nuxt configuration:
// nuxt.config.ts
export default defineNuxtConfig({
// ...
modules: [
// ...
"@pinia/nuxt",
],
});
Stores are created in a stores/
directory, and defined by using Pinia's defineStore
method.
In this example, we have created a store (useCounterStore
) and given the store a name (counter
). We have then defined our state
property (count
) with an initial value.
// stores/counter.ts
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
}),
});
It's as simple as that!
Pinia offers a few ways to access the store and maintain reactivity.
In your component's setup()
, import the store's useStore()
method.
// components/MyCounter.vue
import { useCounterStore } from "@/stores/counter";
export default defineComponent({
setup() {
return {
store: useCounterStore(),
};
},
});
You can now access state through the store instance:
// components/MyCounter.vue
<template>
<p>Counter: {{ store.count }}</p>
</template>
To write cleaner code, you may wish to grab specific properties. However, destructuring the store will break reactivity.
Instead, we can use a computed property to achieve reactivity:
// components/MyCounter.vue
export default defineComponent({
setup() {
const store = useCounterStore();
// โ Bad (unreactive):
const { count } = store;
// โ๏ธ Good:
const count = computed(() => store.count);
return { count };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ store.count }}</p>
</template>
You can destructure properties from the store while keeping reactivity through the use of storeToRefs()
.
This will create a ref for each reactive property.
// components/MyCounter.vue
import { storeToRefs } from "pinia";
import { useCounterStore } from "@/stores/counter";
export default defineComponent({
setup() {
const store = useCounterStore();
// โ Bad (unreactive):
const { count } = store;
// โ๏ธ Good:
const { count } = storeToRefs(store);
return { count };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ store.count }}</p>
</template>
Actions are the equivalent of methods
in components, defined in the store's actions
property.
// stores/counter.ts
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
});
In your component, extract the action from the store.
// components/MyCounter.vue
export default defineComponent({
setup() {
const store = useCounterStore();
const { increment } = store;
const count = computed(() => store.count);
return { increment, count };
},
});
The action can easily be invoked, such as upon a button being clicked:
// components/MyCounter.vue
<template>
<button type="button" @click="increment"></button>
</template>
Getters are the equivalent of computed
in components, defined in the store's getters
property.
Pinia encourages the usage of the arrow function, using the state as the first parameter:
// stores/counter.ts
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
}),
getters: {
getCount: (state) => state.count,
},
actions: {
increment() {
this.count++;
},
},
});
Similarly to state properties, getters need to be accessed in a way that maintains reactivity.
For instance, you could access it through the store instance:
// components/MyCounter.vue
export default defineComponent({
setup() {
const store = useCounterStore();
return { store };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ store.getCount }}</p>
</template>
Or, by using a computed property:
// components/MyCounter.vue
export default defineComponent({
setup() {
const store = useCounterStore();
// โ Bad (unreactive):
const { getCount } = store;
// โ๏ธ Good:
const getCount = computed(() => store.getCount);
return { getCount };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ getCount }}</p>
</template>
Or, by using storeToRefs()
:
// components/MyCounter.vue
import { storeToRefs } from "pinia";
import { useCounterStore } from "@/stores/counter";
export default defineComponent({
setup() {
const store = useCounterStore();
// โ Bad (unreactive):
const { getCount } = store;
// โ๏ธ Good:
const { getCount } = storeToRefs(store);
return { getCount };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ getCount }}</p>
</template>
Since we've discussed actions and getters separately, here is a code snippet that combines both in the style that I recommend:
// components/MyCounter.vue
import { useCounterStore } from "@/stores/counter";
export default defineComponent({
setup() {
const store = useCounterStore();
const getCount = computed(() => store.getCount);
const { increment } = store;
return { getCount, increment };
},
});
// components/MyCounter.vue
<template>
<p>Counter: {{ getCount }}</p>
<button type="button" @click="increment">Increment</button>
</template>
This code has been implemented at lloydtao/nuxt-3-starter/:
How do you think your developer experience will be improved? ๐
Hey, guys! Thank you for reading. I hope that you enjoyed this.
Keep up to date with me:
Resources