DEV Community

Karleb
Karleb

Posted on

First Look At Pinia: A VueJS State Management System

Pinia is one of the newest projects from the Vue ecosystem and it is the new official state management tool for Vue.js apps. Its API is similar to Vuex (its predecessor) and it is designed to be faster and more lightweight. According to Pinia's Official Website, Pinia was born out of the experiment to redesign what a Store for Vue could look like with the Composition API. It is an upgrade on Vuex and compatible with Vue 2 and options API.

Note: This tutorial uses <script setup>, composition API and Vue 3.

Installation

  • Run the following code in your terminal to create a new Vue project.

  • Input the project name in the command prompt.

  • Select Vue.

  • Select yes when the command prompt asks to add Pinia to state management system.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Pinia can be installed in an existing application with npm. Run the following command in your terminal to install Pinia in an existing application.

npm install pinia

//or

yarn add pinia
Enter fullscreen mode Exit fullscreen mode

Project Configuration

The application has to know to use Pinia, so we tell it to use it in the main.js file.

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import './style.css'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')
Enter fullscreen mode Exit fullscreen mode

We imported createPinia from Pinia, instantiated it and instructed the application to use it by simply adding it to the Vue application's instance.

Pinia Store Setup

Create a stores folder in the src folder. In the newly created stores folder, create a products.js file.

The store file is projects.js in our case here

Next, we need to import defineStore in the products.js file and use it.

import { defineStore } from "pinia"

const useProductsStore = defineStore("products", {
  state: () => ({
    products: [
      description:
          "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
        category: "men's clothing",
        image: "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
        rating: { rate: 3.9, count: 120 },]
  }),
  getters: {},
  actions: {},
})
Enter fullscreen mode Exit fullscreen mode

We use the defineStore() method by:

  • Assigning it to a variable. Even though you can name the variable anything, the convention is to start the name use, then the name of the store file in singular and end it with store. In this case, the variable name is useProducutStore

  • The defineStore() takes an optional name which is the name of the store and an options object. This serves as an id for the store.

  • The options object houses the state, setters and actions objects and I have added a products array that contains a single product.

Unlike in Vuex, Pinia does not have a mutations object, the actions are where mutation of state and asynchronous request happens.

The options object is very similar to the options API. The setters are like the computed properties and actions, like methods.

I love this because not being able to put state mutation and methods that make API calls in one object has been a pain. It has led to having methods with similar names both in mutations and actions, the one in actions makes the API call while the one in mutations adds the fetched data into the state.

We are going to add a couple of states and methods to the options object.

import { defineStore } from "pinia"
export const useProductsStore = defineStore("products", {
  state: () => ({
    counter: 0,
    categories: [],
    products: [
      {
        id: 1,
        title: "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
        price: 109.95,
        description:
          "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday",
        category: "men's clothing",
        image: "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg",
        rating: { rate: 3.9, count: 120 },
      },
    ],
  }),
  getters: {
    getCounter: state => state.counter,
    getAllProducts: state => state.products.length,
  },
  actions: {
    incrementCounter() {
      this.counter++
    },
    addProduct (product) {
      if (!product.length) return
      this.products.push(product)
    },
    async getCategories() {
      await fetch("https://fakestoreapi.com/products/categories")
        .then(res => res.json())
        .then(data => {
          this.categories.push(data)
        })
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

In the code above, I have added:

  • categories state to store the categories.

  • counter state to count arbitrary numbers.

  • getCounter() getter to get the current counter's count.

  • getAllProducts() getter all products.

  • incrementCounter() action to increment the counter.

  • addProduct() action to add a product.

  • getCategories() action to make an asynchronous call to an endpoint that gets all categories and populate them to state.

In the actions object, the state is not passed into the functions, rather, they are accessed through this keyword. this return the current store instance. This implies that all actions functions must be regular functions and not arrow functions.

import { defineStore } from "pinia"

export const useProductsStore = defineStore("products", {
 ...
  getters: {
    getCounter() {
      this.counter
    },
    getAllProducts() {
      this.products.length
    },
  },
 ...
})
Enter fullscreen mode Exit fullscreen mode

In the getters objects, the state can be substituted with this keyword, which returns the store instance. Meaning, the arrow functions have to be changed to regular functions.

How to access Pinia getters in another getter

Getters can be accessed and used in another getter using this keyword.

  getters: {
    ...
    getAllProducts() {
      return this.products.length
    },
    doubleProductsSize() {
      return this.getAllProducts * 2
    },
  },
Enter fullscreen mode Exit fullscreen mode

We doubled the size of all the products that were returned by getAllProducts() getter in the new doubleProductsSize() getter.

How to access Pinia actions in another getter

Actions can be accessed and used in another action using this keyword.

 actions: {
    incrementCounter() {
      this.counter = this.counter + 1
    },
   ...
    async getCategories() {

      this.incrementCounter()

      await fetch("https://fakestoreapi.com/products/categories")
        .then(res => res.json())
        .then(data => {
          this.categories.push(data)
        })
    },
  },
Enter fullscreen mode Exit fullscreen mode

We incremented the counter each time we get all products categories by calling incrementCounter() action in the getCategories() async action.

How to access Pinia state in Vue components

In the components, Pinia states are accessed by importing the store in the component and passing it into a storeToRefs() method provided by Pinia where they can be destructured before they are used. storeToRefs() helps to extract the state while keeping its reactivity.

import { useProductsStore } from "./stores/products"
import { storeToRefs } from "pinia"

const store = useProductsStore()
const { categories, counter, products } = storeToRefs(store)
Enter fullscreen mode Exit fullscreen mode

How to access Pinia getters in Vue components

Getters can be accessed on the object instance.

import { useProductsStore } from "./stores/products"
const store = useProductsStore()


 {{ store.getCounter }}
 {{ store.getAllProducts }}
Enter fullscreen mode Exit fullscreen mode

They can also be destructured by passing the store object instance through the storeToRefs() method.

import { useProductsStore } from "./stores/products"
const store = useProductsStore()

const {  getCounter, getAllProducts } = storeToRefs(store)
Enter fullscreen mode Exit fullscreen mode

How to access Pinia actions in Vue components

Action methods can be destructured off the store object instance.


import { useProductsStore } from "./stores/products"
const store = useProductsStore()

const { getCategories } = store

onBeforeMount(() => getCategories())
Enter fullscreen mode Exit fullscreen mode

They can be accessed on the store object instance directly.


import { useProductsStore } from "./stores/products"
const store = useProductsStore()

onBeforeMount(() => store.getCategories())
Enter fullscreen mode Exit fullscreen mode

Conclusion

Pinia is a state management library for Vue.js that provides a way to manage and share reactive states across components in a Vue.js application.

In this tutorial, we learned how to create a Pinia store using the defineStore() function and use it in a Vue.js component. We saw how to define state properties, getters and actions functions that can mutate the state and perform asynchronous calls in the store, and how to access them in a component using the useStore() and storeToRefs() functions provided by Pinia.

Pinia provides a powerful and flexible way to manage states in Vue.js applications, and it's well-suited to large and complex applications where state management can become a challenge. Its use of the Composition API makes it easy to use and understand, and its integration with Vue.js 3 means that it works seamlessly with other Vue.js features and libraries.

Overall, Pinia is a great choice for developers looking for a modern and flexible state management library for their Vue.js applications. It provides a solid foundation for building complex and scalable applications, and its simple and intuitive API makes it easy to learn and use.

Top comments (0)