Learning Pinia

Fundamentals of Pinia, Vue's state management library

ยท

6 min read

Recently I wrote about building my first Vue app. In continuation of my Vue learning the next stop was to understand what state management was all about, and how is it handled in Vue.

My notes on Pinia, after watching another beginner video from Vue Mastery. As always capturing notes and building a small page helps reinforce the concepts, also a place to refer to in the future. Let's go ๐Ÿ

What exactly is State Management?

It is nothing but how a particular component, or a part of the component should behave based on the state of another component. In other words, it is a method to handle, store, and manipulate the shared data of an application across multiple components.

What does it do? It ensures data consistency, synchronization, and efficient updates to the user interface based on data changes.

A simple example to explain would be actions in an e-commerce website. There is a lot of user intervention that happens. Assume a user adds a product to the shopping cart, in this case, the state of the shopping cart changes. As a result, there might be other components that need to be modified. The chosen product needs to be marked as "Added to Cart", the allowed quantity should be reduced, and the user profile may have to keep track of recently added items.

In such a case, state management helps keep track of the state of the component that multiple components depend on. In the above case, it's the state of the shopping cart.

How does Pinia handle State Management?

It maintains a global state that is accessible by all components in the component tree. Whenever a state changes in any component, other components that depend on this state are automatically updated.

Three parts of a Pinia Store

store - This is used to store the global data

actions - This is used to update the state

getters - This uses the state to return a revised version of that state

Time to get our hands dirty ๐Ÿ•

In the book review app that I built earlier, I plan to add a few components that will keep a list of books that I read and books to be read.

Add the following files under components folder

  • ReadBookApp.vue - Parent component to hold the other two

  • ReadBookForm.vue - Simple form to enter the name of the book

  • ReadBookList.vue - List of books added with a provision to mark as read

Add the following file under stores folder

  • readBookList.js - This is the store corresponding to the ReadBookApp

Let's now add a method to add a book to the list and a method to delete a book from the list. Note the id property associated with each book, we need this to refer to the book from other methods.

import { defineStore } from 'pinia'

export const useReadBookListStore = defineStore('readBookList', {
    state: () => ({
        readBookList: [],
        id: 0
    }),
    actions: {
        addBook(item) {
            this.readBookList.push({ item, id: this.id++, completed: false })
        },
        deleteBook(itemId) {
            this.readBookList = this.readBookList.filter((object) => {
                return object.id != itemId
            })
        }
    }
})

Let's now add the form where we input the name of a book and a button that Adds to the list. Note that the input has a v-model=readBook that is reactive through the connection to ref in the script.

Meaning, when the user enters a value in the input field, the property readBookwill be updated.

There is a function addItemAndClear which is invoked when the form is submitted. All it does is add a book to the list and clear the input box so that the user can enter the next book.

<script setup>
    import { ref } from 'vue'
    import { useReadBookListStore } from '@/stores/readBookList'

    const readBook = ref('')
    const store = useReadBookListStore()

    function addItemAndClear(item) {
        if (item.length === 0) {
            return 
        }
        store.addBook(item)
        readBook.value = ''
    }

<template>
    <div>
        <form @submit.prevent="addItemAndClear(readBook)">
            <input v-model="readBook" type="text" /><button>Add</button>
        </form>
    </div>
</template>

The next step is to build the ReadBookList. This just holds the list of books with a checkmark and cross mark. Clicking on checkmark strikes through the book name denoting the book has been read, and clicking on the cross mark deletes the book from the list.

<script setup>
    import { useReadBookListStore } from '../stores/readBookList';
    import { storeToRefs } from 'pinia'

    const store = useReadBookListStore()

    const { readBookList } = storeToRefs(store)

    const { toggleRead, deleteBook } = store

</script>

<template>
    <div v-for="book in readBookList" :key="book.id" class="item">
        <div class="content">
            <span :class=" { completed: book.completed }"> {{  book.item }}</span>
            <span @click.stop="toggleRead(book.id)">&#10004;</span>
            <span @click.stop="deleteBook(book.id)" class="x">&#10060;</span>
        </div>
    </div>
</template>

Two points to note in the above code; one is storeRefs which is a utility method from pinia that helps objects returned from the store retain their reactivity. The second point is the toggleRead method that just strikes through when the checkmark is clicked once, and it disappears when it is clicked again.

Below is the declaration of toggleRead method in stores/readBookList.js

 toggleRead(idToFind) {
            const toRead = this.readBookList.find((obj) => obj.id === idToFind)
            if (toRead) {
                toRead.completed = !toRead.completed
            }
        }

On adding some styles to each component, below is how the page looks like

To summarize what was done

  • We create the required components as a first step.

  • We add the parent component to our main App.vue

  • We then define the store for the component that needs to maintain the state, in our case the BookList.

  • We add the data that needs to be added to the global state which is the booklist

  • We then add the action methods that add/update/delete information from the global state

  • In this example, we did not add any getters that are typically used to return a filtered or revised state

That's it, we can see state management in action. This example is the same as explained in the course, just that I used the label of books.

I'm working on another hypothetical scenario to practice Pinia state management. Here is the case: Divide a page into two. The left side contains 5 stars, 4 stars, 3 stars, 2 stars, and 1 star with a number beside each star. The number denotes how many books have each star rating.

On the right, are the list of books with an option to choose a rating. As and when a user chooses a star rating for a book, the rating on the left-hand side should change accordingly.

I shall update my GitHub repo once I complete this. For now, I plan to deploy the Read Book functionality to Render.

Conclusion

Pinia seems to be quite easy to pick up as the structure is similar to that of a component. State management is such an important aspect of any web app and Pinia does make that easier to implement.

Here is the link to my GitHub repo

Here is the app deployed in Render

Check back after a few days to see the star rating in action โญ

ย