Vue.js State Management Guide: Pinia in Practice

WebCraft Notes - Dec 28 '23 - - Dev Community

Vue.js State Management Guide: Pinia in Practice

Check this post in my web notes

Selecting the right state management solution can often be as critical as choosing the framework itself, particularly when dealing with expansive applications housing numerous components and disparate data sources. In the realm of Vue.js and Nuxt, a variety of options such as Vuex, Pinia, Vuestic, Akita, among others, offer diverse approaches. This article delves into Pinia, an increasingly favored choice, exploring its rising popularity and practical application within the Vue.js ecosystem. Let's look at the aspects of working with Pinia and try it in practice.

Set up a new project

We will create a Vue js project using Quick Start instructions from Vue js official documentation.
Execute the following command to create a vite project:
npm create vue@latest
This command will install and execute create-vue, the official Vue project scaffolding tool.
Once executed, you will be asked some questions.
Vue.js State Management Guide: Pinia in Practice
If you are unsure about an options, simply choose "No" except Pinia in this case we will need it - choose "Yes" . Once the project is created, follow the instructions to install dependencies and start the dev server:
Start dev server
Now you should visit the offered "localhost:" route, and you'll see the initial project. You can remove all components and styles we will not need them.
If you did not install Pinia while configuring a new project you can use
"npm install pinia" command to install it later.

Create an app without state manager

Let's create a simple counter app without state manager and separate functionality between them.
The first one, a simple "Number.vue" component that will receive "number" as props and show them on the screen.

<template>
    <div>
        <p>{{ number }}</p>
    </div>
</template>

<script>
export default {
    name: 'Number',
    props: {
        number: Number
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

The second one, "Controller.vue" component will have two buttons (increase and decrease) which will update the "number" by sending events to the parent component.

<template>
    <div>
        <button @click="$emit('increase');">+</button>
        <button @click="$emit('decrease');">-</button>
    </div>
</template>

<script>
export default {
    name: 'Controller',
}
</script>
Enter fullscreen mode Exit fullscreen mode

The parent component will store all this and take care of the state.

<template>
  <main>
    <div>
      Counter:
    </div>
    <Number :number="number" />
    <div>
      <Controller @increase="number++" @decrease="number--" />
    </div>
  </main>
</template>

<script>
import Number from './components/Number.vue';
import Controller from './components/Controller.vue';

export default {
  name: 'App',
  components: {
    Number,
    Controller
  },
  data() {
    return {
      number: 0
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Okay, all that works correctly and as it was expected. But what if we will have another "middle" component for the controller for example? Then we will have to send props between one more component and catch events the same way, but if want to use data in that middle component then it will be tough. The best solution is the State Manager. The State Manager will store, and update our data and prevent conflicts between components and whom to update data.

Add Pinia to our App

In our project, we will create a "store" folder that will store our state manager files. In the "store" folder add "counter.js" file, this file will contain our counter store. Here is a simple example of a "Counter" store:

import { defineStore } from 'pinia';
export const useCounterStore = defineStore({
    id: 'counter',
    state: () => ({
        counter: 0
    }),
    getters: {
        gCounter: (state) => state.counter
    },
    actions: {
        increment() {
            this.counter++
        },
        decrement() {
            this.counter--
        }
    }
})
Enter fullscreen mode Exit fullscreen mode

Here we use defineStore() function to create Pinia store, and inside brackets send an object with keys:

  • id: identifies store. Important for development because we could have a few different stores, and for identification and debugging we need "id" to be fulfilled.
  • state: a function that returns our data.
  • getters: In Pinia, we can define getters within our store to compute derived values or perform transformations on the state. This will help us to get stored data without its mutation.
  • actions: In Pinia state manager, "actions" refer to methods or functions that are responsible for modifying the state within a store. Nice, now let's modify our components, and use Pinia instead of component data transfering. In "Number.vue" component we will import "useCounterStore" and add store through computed and after we can get our number.
<template>
    <div>
        <p>{{ store.gCounter }}</p>
    </div>
</template>

<script>
import { useCounterStore } from '../store/counter';
export default {
    name: 'Number',
    computed: {
        store() {
            return useCounterStore();
        }
    }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Now, in the "Controller.vue" component we will do the same and receive access to store actions.
Continue reading...

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .