Logo

dev-resources.site

for different kinds of informations.

Adding Pinia to Nuxt 3 🍍 (2023)

Published at
1/1/2023
Categories
nuxt
nuxt3
pinia
Author
tao
Categories
3 categories in total
nuxt
open
nuxt3
open
pinia
open
Author
3 person written this
tao
open
Adding Pinia to Nuxt 3 🍍 (2023)

Introduction

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.

State management

If you've ever found yourself aimlessly trying to manage state through props and events, then the idea of a store may sound appealing:

  • Manage an app's state from a single, centralised store
  • Update and retrieve data through simple actions and getters
  • Subscribe to changes to achieve deep reactivity without much work

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:

Demo of counter component

Pinia

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?

  • Deep reactivity by default
  • No explicit mutations (all changes are implicit mutations)
  • Analogous with Options API:
    • Actions (equivalent of methods)
    • Getters (equivalent of computed)

Installation

Official documentation for using Pinia with Nuxt can be found here.

Install the package:



yarn add @pinia/nuxt


Enter fullscreen mode Exit fullscreen mode

Add the module to your Nuxt configuration:



// nuxt.config.ts

export default defineNuxtConfig({
  // ...
  modules: [
    // ...
    '@pinia/nuxt',
  ],
})


Enter fullscreen mode Exit fullscreen mode

Creating a store

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,
  }),
})


Enter fullscreen mode Exit fullscreen mode

It's as simple as that!

Using the store

Pinia offers a few ways to access the store and maintain reactivity.

1. Store instance

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(),
    }
  },
})


Enter fullscreen mode Exit fullscreen mode

You can now access state through the store instance:



// components/MyCounter.vue

<template>
  <p>Counter: {{ store.count }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

2. Computed properties

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ store.count }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

3. Extract via storeToRefs()

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ store.count }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

Actions

Adding an action

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++
    },
  },
})


Enter fullscreen mode Exit fullscreen mode

Using an action

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode

The action can easily be invoked, such as upon a button being clicked:



// components/MyCounter.vue

<template>
  <button type="button" @click="increment"></button>
</template>


Enter fullscreen mode Exit fullscreen mode

Getters

Getters are the equivalent of computed in components, defined in the store's getters property.

Adding a getter

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++
    },
  },
})


Enter fullscreen mode Exit fullscreen mode

Using a getter

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ store.getCount }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ getCount }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ getCount }}</p>
</template>


Enter fullscreen mode Exit fullscreen mode

A complete component

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 }
  },
})


Enter fullscreen mode Exit fullscreen mode


// components/MyCounter.vue

<template>
  <p>Counter: {{ getCount }}</p>
  <button type="button" @click="increment">Increment</button>
</template>


Enter fullscreen mode Exit fullscreen mode

This code has been implemented at lloydtao/nuxt-3-starter/:

Demo of counter component

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:

nuxt3 Article's
30 articles in total
Favicon
Renderización Dinámica de Componentes en Vue 3 y Nuxt 3: Guía Práctica y Caso Real
Favicon
Nuxt3 CSR Background Image Lazy loading
Favicon
DartsMash: a platform for darts enthusiasts
Favicon
Nuxt3 : limitation on Layers & Modules
Favicon
Using postgresql with nuxt3 by ORM(sequelize)
Favicon
Nuxt3 : API error handling
Favicon
Nuxt Icon
Favicon
Integrating Nuxt 3 with Recaptcha v3 for Token Handling 🤖🔐
Favicon
Nuxt 3 Starter
Favicon
Nuxt 3 Builder: A Tailored Editor for Developers
Favicon
Build an X clone w/ Nuxt UI
Favicon
Nuxt3 Form with Feedback
Favicon
Custom 404 Page in Nuxt
Favicon
Nuxt 3 authentication with pinia
Favicon
Nuxt 3, UnoCSS, and Preset rem to px
Favicon
Configuração e instalação do Nuxt 3
Favicon
The DevOnly component in Nuxt 3: A developer's best friend
Favicon
Storyblok Nuxt 3 news 🗞
Favicon
Implementing OpenID Connect (OIDC) Authentication with Nuxt 3
Favicon
Authentication in Nuxt 3
Favicon
Adding ESLint and Prettier to Nuxt 3 ✨ (2024)
Favicon
API Management in Nuxt 3 with TypeScript
Favicon
Docker and Nuxt 3
Favicon
Pinia and Nuxt 3
Favicon
Adding Pinia to Nuxt 3 🍍 (2023)
Favicon
Adding Vitest to Nuxt 3 ⚡ (2023)
Favicon
Adding Tailwind CSS to Nuxt 3 🍃 (2023)
Favicon
How I set up eslint in my projects
Favicon
How to create and minify Vuetify 3 Nuxt 3 project bundle
Favicon
Build an Intercom clone in Nuxt.js - Part Two

Featured ones: