Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Migrating a Vue.js App to Vuex

DZone's Guide to

Migrating a Vue.js App to Vuex

Curious about Vue's application data store? Read on to learn how to move your exiting Vue.js project to Vuex, and get an expert's opinion on what this does.

· Web Dev Zone
Free Resource

Get deep insight into Node.js applications with real-time metrics, CPU profiling, and heap snapshots with N|Solid from NodeSource. Learn more.

One of the difficult things about getting started with Vuex is that it is not so much a library as it is a design pattern. It follows that implementing Vuex is not so much about using an API, as it is about structuring your code to comply with the pattern. If you're new to Vuex, this will be daunting.

In this article, I'll demonstrate how to get started migrating Vuex into an existing Vue.js project. I'll show you how to identify the parts of your app's state that belong in Vuex, and those that don't, how to refactor your component functions into mutations, actions and so on, and finally we'll discuss the benefits accrued.

If you're not sure why you should use Vuex, I recommend you read this post first WTF Is Vuex: A Beginner’s Guide to Vue’s Application Data Store.

Note: this article was originally posted here on the Vue.js Developers blog on 2017/08/15

Case Study: Vue.js Cinema

As a case study, we'll migrate a demonstration app I made called Vue.js Cinema to Vuex. It's a single-file component-based Vue app that is a good enough candidate for Vuex as it has a significant amount of application state.

Image title

If you want to learn how to build Vue.js Cinema from scratch, it's part of my Ultimate Vue.js Developers course.

Remember that Vuex's purpose is to manage application state. It follows that in order to migrate Vue.js Cinema to Vuex, we must identify its state. We'll see the state in the code shortly, but it's helpful to first infer it by simply observing what the app does, i.e. it displays a list of movies and session times that can be filtered by changing the day, or toggling time and genre filters.

What State Belongs in Vuex?

By using Vue Devtools, or just inspecting the code, we can see the data of the instance and each component:

Image title

Our objective is not to move all data to the Vuex store. Instead, we want to target data which:

  1. Changes throughout the lifecycle of the application (static data doesn't need much management).
  2. Is shared by more than one instance/component.

We when say application state, this is what we're talking about, and this is what we want to move into the store.

For example, look at this bank of check-filter components, which are custom checkboxes used to filter the movie list:

Image title

Each check-filter component has a checked data property, and also a title that is passed to it as a prop, e.g. Before 6 pm, After 6 pm, etc.:

src/components/CheckFilter.vue

<template>...</template>
<script>
  export default {
    data() {
      return {
        checked: false
      }
    },
    props: [ 'title' ],
    ...
  }
</script>

The checked data property is text-book application state, as it changes throughout the lifecycle of the app, and other components will certainly need to react to its state.

The title prop, on the other hand, does not change and does not affect any other component. It, therefore, does not need to be managed by Vuex.

Note: if you have any local state that's important enough that you want to track it in devtools, then it is fine to add it to the Vuex store; it's not a violation of the pattern.

Props → State

If you've modularised your application into components, as I have with Vue.js Cinema, a good place to start looking for application state is your component props, and more specifically, props across multiple components that share the same data.

For example, I bound the data property day from the root instance of this application to the day-filter and movie-list components. The day select is how a user selects what day of the week they want to see session times for, and the movie list is filtered by the selected value.

Image title

The day-select component

To manage day in Vuex we can simply delete it from the root instance and move it to the state object in the Vuex store:

export default new Vuex.store({
  state: {
    day: moment() // the initial value
  },
  ...
});

We can then delete the binding from the template i.e. v-bind:day="day", and in the component, replace the prop with a computed property that calls the store. In the case of day-select:

export default {
  props: [ 'day' ],
  ...
}

Goes to:

export default {
  computed: {
    day() {
      return this.$store.state.day
    }
  }
}

Events → Mutations

While props are about sharing data across components, events intend to change it. The event listeners in your code should be considered for conversion to Vuex mutations. Event emitters will then be refactored to commit a mutation, rather than emit an event.

To return to the example of the day-select component, whenever a user changes the day by clicking the appropriate piece of UI, this component method is called:

selectDay(day) {
  this.$emit('setDay', day);
}

The root instance has a listener to respond to the custom setDay event:

created() {
  this.$on('setDay', day => { this.day = day; });
}

In the case of the listener, it can be transferred from the root instance and put into the mutations object of the store with only slight modification:

src/store/index.js

export default new Vuex.Store({
  state: {
    day: moment()
  },
  mutations: {
    setDay(state, day) {
      state.day = day;
    }
  }

A mutation commit will now replace the event emit in the day-select component:

selectDay(day) {
  this.$store.commit('setDay', day);
}

We can now remove the event listener binding in the template as well, i.e. v-on:click="setDay".

AJAX → Actions

Actions in Vuex are the mechanism for handling asynchronous mutations. If you're using AJAX to update your application state, you'll probably want to wrap it in an action.

In Vue.js Cinema, I use AJAX (via the Vue Resource HTTP client) to populate the movie data from an API endpoint on my server:

src/main.js

created() {
  this.$http.get('/api').then(response => {
    this.movies = response.data;
  }); 
}

This only happens once in the application lifecycle, when it is created, so it does seem somewhat trivial to log this with Vuex, but there's no harm and a lot to gain in terms of debugging.

Actions are dispatched from the application and must commit a mutation once the asynchronous event completes. Here's the code to add to the store:

src/store/index.js

export default new Vuex.Store({
  state: {
    ...
    movies: []
  },
  mutations: {
    ...
    setMovies(state, movies) {
      state.movies = movies;
    }
  },
  actions: {
    getMovies({ commit }) {
      Vue.http.get('/api').then(response => {
        commit('setMovies', response.data);
      });
    }
  }
});

The created hook will now just dispatch an action:

src/main.js

created() {
  this.$store.dispatch('getMovies');
}

Results and Benefits

Once Vue.js Cinema has been migrated to Vuex, what are the tangible benefits? There's probably not going to be any direct benefits to the user in terms of better performance, etc., but it will make the life of the developers easier.

Debugging

Now that the application data is being managed by Vuex, we can easily view any changes to it in Vue Devtools:

Image title

Vuex and Vue Devtools not only log a change, and the source of the change but allow you to "rewind" the change so you can see exactly how it affects the application.

As creator Evan You said: "The benefit of Vuex is that changes going through the store are trackable, replayable, and restorable."

Decoupling of Components and State

In Vue.js Cinema, we track the checked filters in two arrays, one for time and one for genre. Adding and removing filters requires a function that was previously a method on the root instance. Using Vuex, we can abstract that logic into a mutation:

src/store/index.js

mutations: {
    ...
    checkFilter(state, { category, title, checked }) {
      if (checked) {
        state[category].push(title);
      } else {
        let index = state[category].indexOf(title);
        if (index > -1) {
          state[category].splice(index, 1);
        }
      }
    }
  },

The advantage of this kind of decoupling is that it makes the code more logical and maintainable.

Condensing Templates

Without a store pattern like Vuex, we share data between components through the mechanisms of props and events, which need to be declared in a component's template. In a large application, this can make templates quite verbose.

Managing shared data in Vuex allows this:

<movie-list
  :genre="genre" 
  :time="time" 
  :movies="movies" 
  :day="day"
></movie-list>

To go to:

<movie-list></movie-list>
Get the latest Vue.js articles, tutorials, and cool projects in your inbox with the Vue.js Developers Newsletter

Node.js application metrics sent directly to any statsd-compliant system. Get N|Solid

Topics:
vue.js ,vuejs ,javascript ,front end development

Published at DZone with permission of Anthony Gore, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}