Vue State — Migrate From Vuex Store to Pinia

How to migrate Vue.js applications from Vuex to Pinia.
Jun 23 2022 · 5 min read

Introduction 

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!

Background

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.

  1. Directory structure
  2. Define and Use Pinia Store
  3. Understanding Pinia architecture
  4. Example code of migration from Vuex to Pinia

Feel free to jump to any section you are interested in.

So without wasting time, Let’s go further!

1. Directory structure

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.

2. Define and Use the Pinia store

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()

3. Understanding Pinia architecture

In the Vuex store, we have 4 fields.

  1. State — Decide the state of the store
  2. Getters — Returns the value of the store
  3. Actions — Do action to update the store state
  4. Mutations — Update store state

In the Pinia store, we have 2 required fields and 1 conditionally required field.

  1. State — Decide the state of the store
  2. Getters — Require only for computed properties
  3. Actions — Do actions and update the store state

Let’s convert all fields step by step in pinia.


a. State

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,
    };
}

b. Getters

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
    },
}

c. Mutations

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'

d. Actions

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.

4. Example code of migration from Vuex to Pinia

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.

Conclusion

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.

Similar articles


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.


sumita-k image
Sumita Kevat
Sumita is an experienced software developer with 5+ years in web development. Proficient in front-end and back-end technologies for creating scalable and efficient web applications. Passionate about staying current with emerging technologies to deliver.

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.