DEV Community

Cover image for How to map your VueX data to Vue models
Amin
Amin

Posted on

How to map your VueX data to Vue models

Cover image taken from here.

I really love Vue.js. And I adore VueX. It is to me the simplest & sexiest implementation of the Flux architecture for someone like me who is a total dumdum. Actually, I only learned what the Flux architecture was when I experienced with Elm, which is another framework written in an entirely different language but compiles to JavaScript. But I had no problem using VueX before and that's why it is so powerful to me.

So VueX is great at sharing data through your components without having to implement a hierarchical cascade of parent-to-child data transmission through props, which can be hard to maintain.

But VueX is not perfect, and in some case it can be as tedious as before to use it, especially when dealing with data that can both retrieved and updated. Let's build a simple application illustrating this idea.

Our application will be a dead simple web page, with a level one title saying hello to you and an input to where to type your name. Here is what we need.

HTML

Ideally, here is what we would want to have in the end.

<!doctype html>
<html>
  <body>
    <div id="app">
      <h1>Hi, {{ name }}</h1>
      <input type="text" v-model="name">
    </div>
    <script type="module" src="./index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

I know, this is not a fully compliant HTML page but it is just for the sake of the demo. As you can see, we want to have a name data that would store our name. And an input reacting to the input even and setting the name we typed by replacing the old one stored in our data. Pretty simple as a starter. But what will be challenging is to use this exact same syntax, without changing a thing here, while using VueX to store & update our name.

Think about how you would do that in Vue.js. If you don't know how, then you have come to the right place and this article is for you!

Store

Here is what our store would look like ideally.

import Vue from "./vue.js";
import VueX, { Store } from "./vuex.js";

Vue.use(VueX);

export default new Store({
  state: {
    name: "Jhon"
  },
  getters: {
    getName(state) {
      return state.name;
    }
  },
  mutations: {
    setName(state, newName) {
      state.name = newName;
    }
  },
  actions: {
    SET_NAME(context, newName) {
      context.commit("setName", newName);
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

So, if I want to retrieve my name, I would want to use the getter getName which would lookup in the store for the name property store in the state object of our store. And if I wanted to update my name, I would want to trigger an action called SET_NAME with the new name I just typed in my input, which would in the end use the setName mutation to update the name property in the state.

Now that our store is setup, we need to use it.

import Vue from "./vue.js";
import { mapGetters, mapActions } from "./vuex.js";
import store from "./store.js";

new Vue({
  el: "#app",
  store,
  methods: {
    ...mapActions([ "SET_NAME" ]),
    ...mapGetters([ "getName" ])
  }
});
Enter fullscreen mode Exit fullscreen mode

Here, I define a new Vue application just as you would do for your own. I import my store that I use in my app and also import the mapGetters & mapActions helpers from VueX to help me re-use my getter & action more easily in my application. It would be equivalent to use these instructions instead.

this.$store.state.name; // to access our name
this.$store.commit("setName", "JOhn DOE"); to update our name
Enter fullscreen mode Exit fullscreen mode

But it is considered a good practice to not access directly our state and use actions to commit our changes. This is just a matter of preferences I guess in most situtations.

Now that everything is setup, we need to find a solution to make our markup work. Because in the actual setup, our code wont work and Vue.js will actually complain because we have no state property named name here.

This is where things get interesting. You can actually construct an object that will be both a getter and a setter.

Let's take a step back and use a simple example to understand how getter/setter properties work.

"use strict";

const motorcycle = {
  horsepower: "45hp"
};

console.log(motorcycle.horsepower);
// 45hp

motorcycle.horsepower = 90;

console.log(motorcycle.horsepower);
// 90
Enter fullscreen mode Exit fullscreen mode

In this example, I want to display the motorcycle's horsepower, set it and display it again after its upgrade. But the output is inconsistent, and frankly not human-friendly. One way we could use to solve this problem is the getter/setter trick.

"use strict";

const motorcycle = {
  _horsepower: 45,
  get horsepower() {
    return `${this._horsepower}hp.`;
  },
  set horsepower(newHorsepower) {
    this._horsepower = newHorsepower;
  }
};

console.log(motorcycle.horsepower);
// 45hp.

motorcycle.horsepower = 90;

console.log(motorcycle.horsepower);
// 90hp.
Enter fullscreen mode Exit fullscreen mode

Now we have a consistent output! That is how they work in plain JavaScript: whenever we want to display the property, the corresponding get PROPERTY() function will be triggered, same things goes for the update of the property with the corresponding set PROPERTY(newValue). In Vue.js, they are implemented in a special way, but you'll feel at home quickly when you'll see how to use them. Let's go back to our code and implement this so called computed getter/setter property.

import Vue from "./vue.js";
import { mapGetters, mapActions } from "./vuex.js";
import store from "./store.js";

new Vue({
  el: "#app",
  store,
  methods: {
    ...mapActions([ "SET_NAME" ]),
    ...mapGetters([ "getName" ])
  },
  computed: {
    name: {
      get() {
        return this.getName();
      },
      set(newName) {
        return this.SET_NAME(newName);
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

As you can see, it changes a bit, but it is really similar to what we did in JavaScript. We have a name property which is dynamically reacting to displays & changes. And instead of a custom handling, we used what we defined in our store. That simple! For the record, here is what we have for our markup (unchanged).

<!doctype html>
<html>
  <body>
    <div id="app">
      <h1>Hi, {{ name }}</h1>
      <input type="text" v-model="name">
    </div>
    <script type="module" src="./index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

And now, our code is 100% working, no hack needed. You may have done something like that in the past when dealing with your store.

<!doctype html>
<html>
  <body>
    <div id="app">
      <h1>Hi, {{ name }}</h1>
      <input
        type="text"
        @input="$event = $store.commit('setName', $event.target.value)"
        :value="this.$store.state.name">
    </div>
    <script type="module" src="./index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

But I think that we can all agree that using computed getter/setter properties are way cooler and sexier. The documentation for this two-way computed property is available here. But I had to write this post to be sure that everyone get the idea behind this feature. Plus, they didn't do an example using the mapGetters & mapActions, so here is that. And if you are not using them, you should! There is also some more informations on the official documentation.

And this is all folks. I hope you enjoyed this little trick. I'm now using it at work and in my side-projects extensively since I discovered this neat trick.

Top comments (1)

Collapse
 
raheel98 profile image
Raheel Khan

There is so much useful information here in such a concise article. Thank you, this is very helpful!