Photo by Julien Pianetti on Unsplash
Learning Pinia
Fundamentals of Pinia, Vue's state management library
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 twoReadBookForm.vue
- Simple form to enter the name of the bookReadBookList.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 readBook
will 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)">✔</span>
<span @click.stop="deleteBook(book.id)" class="x">❌</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 โญ