KodaSchool
Learn To Code
State management in Vue.js using Pinia
Getters are similar to computed properties in Vue.js. They allow you to derive or compute new state values based on the store’s existing state.
topics
One of the most vital aspects of frontend development is state management. State management is the process of consistently persisting the status of variables or user interface controls such as buttons and text fields across multiple user interfaces or components. Simply put, what components need to know what, at what time. In simple projects it is quite easy, but as the complexity of the application increases more components are involved and it becomes a pain in the ass, most cases as the complexity grows, developers resort to state management libraries like Pinia.
How to manage state in fairly simple projects
In a case where you want to manage state in a simple project, the logical way would be to use props. That would mean passing a value from the parent component to its children and since Vue’s prop system follows a one-way data flow model where data flows from parent to child, the child component cannot directly modify a prop. It has to notify the parent about the changes using custom events.
Parent Component
<!-- ParentComponent.vue -->
<template>
<div>
<h1>Parent Component</h1>
<p>Parent Count: {{ count }}</p>
<ChildComponent :count="count" @increment="incrementCount" />
</div>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent,
},
setup() {
// State (reactive variable) in the parent component
const count = ref(0);
// Method to increment count
const incrementCount = () => {
count.value++;
};
return {
count,
incrementCount,
};
},
};
</script>
Breakdown
The count
is passed down as a prop to ChildComponent
while he incrementCount
method is passed as a custom event listener (@increment
), which will be triggered by the child component.
Child Component
<!-- ChildComponent.vue -->
<template>
<div>
<h2>Child Component</h2>
<p>Child Count (from parent): {{ count }}</p>
<button @click="emitIncrement">Increment Count</button>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
count: {
type: Number,
required: true,
},
},
setup(props, { emit }) {
// Emit custom event to parent to trigger the count increment
const emitIncrement = () => {
emit('increment');
};
return {
emitIncrement,
};
},
});
</script>
Breakdown
The props (count
) are received in the setup()
function using props
, the emitIncrement
method triggers the custom event by calling emit('increment')
, informing the parent to increment the count. In this case the props.count
is displayed in the template but is not modified directly by the child component.
Quite simple, but for a case where you have multiple sibling components each with they own nested children it becomes complex.
Pinia
Pinia is a state management library built specifically for Vue.js, it provides a flexible and intuitive way to manage state within Vue applications, offering a more streamlined alternative to Vuex. It is built on top of vue’s composition API which makes working with states more reactive.
Getting Started with Pinia
To get started with Pinia we need to first of all install it by running this command in your project.
npm install pinia
After installation we need to initialize and register Pinia in the main entry file( main.js or main.ts )
// main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia(); // Create the Pinia instance
app.use(pinia); // Register Pinia with the Vue app
app.mount('#app');
Creating a Pinia store
In Pinia, state is organized into stores that define it and the actions that can be performed on that state.
State
State is where you store your application’s reactive data in Pinia. It acts as a single source of truth for your components, holding all shared state that multiple components may need to access. Pinia automatically makes state reactive, meaning any changes to the state are immediately reflected in the components using it.
Actions
Actions are functions defined in the store that allow you to modify the state or perform side effects such as making API request.
Getters
Getters are similar to computed properties in Vue.js. They allow you to derive or compute new state values based on the store’s existing state. Getters are cached and will only recompute when their dependencies change. Getters are reactive, meaning that if the state they depend on changes, the computed value automatically updates wherever it is used.
Converting our previous example using pinia
First, we need to create a Pinia store that manages the count
state and the increment action.
// stores/counter.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
// State: defining a reactive state variable
const count = ref(0);
// Action: defining a method to increment the count
const increment = () => {
count.value++;
};
// Returning state and actions
return {
count,
increment,
};
});
Next we need to convert the parent component to interact with the Pinia store instead of maintaining its own local state.
<!-- ParentComponent.vue -->
<template>
<div>
<h1>Parent Component</h1>
<p>Parent Count: {{ count }}</p>
<ChildComponent />
</div>
</template>
<script>
import { useCounterStore } from './stores/counter';
import ChildComponent from './ChildComponent.vue';
import { useStore } from 'pinia';
export default {
components: {
ChildComponent,
},
setup() {
// Accessing the Pinia store
const counterStore = useCounterStore();
return {
count: counterStore.count, // Accessing the state from the store
};
},
};
</script>
The child component can now directly interact with the store by accessing and modifying the shared state.
<!-- ChildComponent.vue -->
<template>
<div>
<h2>Child Component</h2>
<p>Child Count (from store): {{ count }}</p>
<button @click="increment">Increment Count</button>
</div>
</template>
<script>
import { useCounterStore } from './stores/counter';
export default {
setup() {
// Accessing the Pinia store
const counterStore = useCounterStore();
return {
count: counterStore.count, // Accessing the state from the store
increment: counterStore.increment, // Action to increment the count
};
},
};
</script>
Conclusion
Pinia simplifies state management by creating a centralized state, a single point of truth, This allows both the parent and child components to access and manipulate the same piece of state without needing to pass props or emit events. It also creates a shared store where both the parent and child components use the useCounterStore()
function to access the store. They can directly read the count
value and call the increment
action. Pinia also ensures that any changes to the count
value in the store are reactive. As a result, both components automatically update when the state changes.
Peace!
share
I enjoy working with front-end technologies like Vue, React, and Vanilla JavaScript, with some Python on the side. I love building interactive web experiences and breaking down tricky concepts to make them easier to understand. I'm always curious and enjoy learning new things in the ever-evolving tech space.