First Look At Pinia: A VueJS State Management System
Tags: vue, pinia, javascript
Published: 2023-02-27 23:08:58

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](https://pinia.vuejs.org/introduction.html), 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.
    

```javascript
npm create vite@latest
```

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

```javascript
npm install pinia

//or

yarn add pinia
```

### **Project Configuration**

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

```javascript
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')
```

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.

```javascript
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: {},
})
```

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.

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

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.

```javascript
import { defineStore } from "pinia"

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

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.

```javascript
  getters: {
    ...
    getAllProducts() {
      return this.products.length
    },
    doubleProductsSize() {
      return this.getAllProducts * 2
    },
  },
```

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.

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

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.

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

const store = useProductsStore()
const { categories, counter, products } = storeToRefs(store)
```

### **How to access Pinia getters in Vue components**

Getters can be accessed on the object instance.

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


 {{ store.getCounter }}
 {{ store.getAllProducts }}
```

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

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

const {  getCounter, getAllProducts } = storeToRefs(store)
```

### How to access Pinia actions in Vue components

Action methods can be destructured off the store object instance.

```javascript

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

const { getCategories } = store

onBeforeMount(() => getCategories())
```

They can be accessed on the store object instance directly.

```javascript

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

onBeforeMount(() => store.getCategories())
```

### 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.