Pinia is recently gaining popularity among Vue developers due to its simplicity.
Until now, we were mostly using Vuex for the application state management in Vue. Let’s give Pinia a try today and see how it goes.
This article will guide you in a simple way to migrate Vue.js applications from Vuex
to Pinia
.
Note that Pinia also works fine with Options API, so no worries if you are not using composition APIs yet.
We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!
Recently, we have migrated our canopas website from Vuex to Pinia. We feel it's super easy.
It is quite similar to Vuex but with less code. It has reduced lots of coding and extra computations of mutations and getters.
Pinia can work with Vuex easily. So if you have a large application then don’t worry. You can replace the store with Pinia module-wise; eventually, you can have Pinia store in the whole application.
Today’s article consists of 4 sections.
Feel free to jump to any section you are interested in.
So without wasting time, Let’s go further!
On the website, we have a jobs
module, in which we have two APIs — fetch all jobs
and get job by id
. We have created two different stores for each.
Vuex is storing all states in a single store. But Pinia can have multiple stores as per requirements. So its directory name should be stores
instead of store
.
Create a directory with the name stores
in Vue’s src
directory.
The directory structure could be,
src
└── stores
├── jobs.js # 'jobs' store
└── job-by-id.js # 'jobsById' store
Now, let’s start conversion.
We can define store in stores/jobs.js
,
import { defineStore } from 'pinia'
export const useJobsStore = defineStore('jobs', {
// states and actions here
})
jobs
also referred to as id, is necessary and is used by Pinia to connect the store to the dev tools. Naming the returned function use…
is a convention of Pinia.
We can use this jobs store in components as follows,
import { useJobsStore } from '@/stores/jobs'
const store = useJobsStore()
In the Vuex store, we have 4 fields.
State
— Decide the state of the storeGetters
— Returns the value of the storeActions
— Do action to update the store stateMutations
— Update store stateIn the Pinia store, we have 2 required fields and 1 conditionally required field.
State
— Decide the state of the storeGetters
— Require only for computed propertiesActions
— Do actions and update the store stateLet’s convert all fields step by step in pinia.
The pinia state is defined as a function that returns the store's initial state. and we can access it directly without getters like store.items
.
In options API, if we have computed or methods hooks, we can access state using mapState
helper,
...mapState(useJobsStore, ["items","error"])
You can see the difference in both store states in the below snippet.
/* vuex store state
To access it, we need getters like, store.getters.jobs
*/
const state = {
jobs: null,
jobById: null,
jobsError: null,
};
/*
pinia store state
We have now indivdual files for apis, items can be considered as jobs
We can access state directly as store.items
*/
const state = () => {
return {
items: null,
error: null,
};
}
Getters are only used for computed properties like fullname
, we can access state properties (In our case it is jobs
) directly with the store.jobs
For computed properties, getters can be defined as,
getters: {
fullname: (state) => state.firstname + state.lastname,
},
We can access it with this in other getters like,
getters: {
greetings() {
return "Hello " + this.fullname
},
},
We can use them the same as a state. More details about pinia’s getters are here.
Review the following code for getters in both stores.
/*
vuex store getters
*/
const getters = {
jobs: (state) => state.jobs,
jobById: (state) => state.jobById,
jobsError: (state) => state.jobsError,
}
/*
pinia store getters
We don't need any computed getters in the website, so it has been completely removed in our case.
But here is example of pinia getters
*/
const getters = {
fullname: (state) => state.firstname + state.lastname,
greetings() {
return "Hello " + this.fullname
},
}
Mutations were removed from Pinia, as the task of mutations can be done by actions themselves. Or you can assign values directly to store within components like store.firstName = 'Myname'
Actions are the same as vuex actions, but it has removed the dependency of context. Instead of getting a state with context
, we can access it with this
directly.
/*
vuex store actions
*/
const actions = {
getJobs({ commit }) {
return new Promise((resolve, reject) => {
axios
.get(BASE_API_URL + "/api/careers")
.then((response) => {
commit("SET_JOBS", response.data);
resolve(response);
})
.catch((error) => {
commit("SET_JOBS_ERROR", error);
reject(error);
});
});
},
}
/*
pinia store actions
*/
const actions = {
getJobs() {
return new Promise((resolve) => {
axios
.get(BASE_API_URL + "/api/careers")
.then((response) => {
//removed mutation and update store directly using this
this.items = response.data;
resolve();
})
.catch((error) => {
//removed mutation and update store directly using this
this.error = error;
resolve();
});
});
},
}
We can access actions in components in the following way.
For options API using mapActions
helper,
import { mapActions } from "pinia";
...mapActions(useJobsStore, ["getJobs"])
For composition API, in setup(),
setup() {
const store = useJobsStore()
store.getJobs()
}
More details about Pinia's actions are here.
Vuex =>store/jobs.js
,
import axios from "axios";
const defaultState = {
jobs: null,
jobById: null,
jobsError: null,
};
const getters = {
jobs: (state) => state.jobs,
jobById: (state) => state.jobById,
jobsError: (state) => state.jobsError,
}
const actions = {
getJobs({ commit }) {
return new Promise((resolve, reject) => {
axios
.get(BASE_API_URL + "/api/careers")
.then((response) => {
commit("SET_JOBS", response.data);
resolve(response);
})
.catch((error) => {
commit("SET_JOBS_ERROR", error);
reject(error);
});
});
},
getJobById({ commit }, data) {
return new Promise((resolve, reject) => {
axios
.get(BASE_API_URL + "/api/careers/" + data.jobId)
.then((response) => {
commit("SET_JOB_BY_ID", response);
resolve(response);
})
.catch((error) => {
commit("SET_JOBS_ERROR", error);
reject(error);
});
});
},
}
const mutations = {
SET_JOBS: (state, jobs) => {
state.jobs = jobs;
state.jobsError = null;
},
SET_JOB_BY_ID: (state, data) => {
state.jobById = data.job;
state.jobsError = null;
},
SET_JOBS_ERROR: (state, jobsError) => {
state.jobsError = jobsError;
},
}
export default {
state: defaultState,
getters,
actions,
mutations,
};
Pinia => stores/jobs.js
import { defineStore } from "pinia";
import axios from "axios";
export const useJobListStore = defineStore("jobs", {
state: () => {
return {
items: null,
error: null,
};
},
actions: {
getJobs() {
return new Promise((resolve) => {
axios
.get(BASE_API_URL + "/api/careers")
.then((response) => {
this.items = response.data;
resolve();
})
.catch((error) => {
this.error = error;
resolve();
});
});
},
},
});
Pinia =>stores/job-by-id.js
import { defineStore } from "pinia";
import axios from "axios";
export const useJobDetailStore = defineStore("job-detail", {
state: () => {
return {
item: null,
error: null,
};
},
actions: {
getJobById(id) {
return new Promise((resolve) => {
axios
.get(BASE_API_URL + "/api/careers/" + id)
.then((response) => {
this.item = response.data;
resolve();
})
.catch((error) => {
this.error = error;
resolve();
});
});
},
},
});
Quite easy. Now jobs module will be using the pinia store.
As per our experience, Pinia is an easy-to-use and handy option for the Vuejs state management. Even if we have a large project to migrate, we can easily migrate it to Pinia.
That’s it for today.
We’re Grateful to have you with us on this journey!
Suggestions and feedback are more than welcome!
Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.