Filtering data in Vuex

Like most challenges in software development, there’s usually more than one way to implement a solution. Filtering data that resides in Vuex is no exception. This is only one potential implementation and depending on your situation, this may or may not be applicable. For me, it worked out just fine. 

The Setup

So, I have this sample application called The Presidents. 

I bet by looking at it you can guess what it does. It displays the US presidents. Fancy!

To keep things simple, I have an Express server running in node locally and when the application loads the App.vue component, a call is made to Vuex to load the data.  Here is the code from the App component:

<script>
import HeaderComponent from "./components/Header";
import { mapGetters } from "vuex";

export default {
  name: "app",
  components: {
    "header-component": HeaderComponent
  },
  created() {
    this.$store.dispatch("getPresidents");
  }
};
</script>

And the getPresidents() action in Vuex:

import axios from 'axios';

const state = {
    presidents: []
};

const actions = {
    getPresidents({commit}) {
        axios.get('/api/presidents').then((response) => {
            commit('UPDATE_PRESIDENTS', response.data);
        });
    }
};

const mutations = {
    UPDATE_PRESIDENTS(state, payload) {
        state.presidents = payload;
    }
};

Once we make the call and load the data, it’s displayed in the PresidentList component. If we think about the UI in terms of components, here’s how it shakes out:

We have the main App component, denoted in red, the header, filter and presidentList components denoted in blue and the PresidentListItem component denoted in Yellow.

There is a getter on the Vuex store that is accessed by the PresidentList Component and a PresidentListItem component is rendered for each president.

So far so good. 

Lets turn our attention to the Filter Component.

Filtering in Vuex

I want to be able to filter the president list based on the name, the party affiliation or both. 

My initial implementation passed two strings to the Vuex store but I really didn’t care for that setup so I decided to create a custom object called filterObj.

filterObj will make it easy to keep track of the filter criteria. My custom object also provided me the ability to easily see if I was dealing with 1 or 2 filter criteria.

Here is the code for the custom filterObj.

const filterObj = {
    _presidentName: undefined,
    _partyAffiliation: undefined,
    _filterCount: 0
}

Object.defineProperty(filterObj, "presidentName", {
    enumerable: true,
    get(){
        return this._presidentName;
    },
    set(newVal) {
        if(newVal === ''){
            this._presidentName = undefined;
            this._filterCount--;
        } else {
            if(this._presidentName === undefined){
                this._filterCount++;
            }
            this._presidentName = newVal.toLowerCase();
        }
    }
});

Object.defineProperty(filterObj, "partyAffiliation", {
    enumerable: true,
    get(){
        return this._partyAffiliation;
    },
    set(newVal) {
        if(newVal === ''){
            this._partyAffiliation = undefined;
            this._filterCount--;
        } else {
            if(this._partyAffiliation === undefined){
                this._filterCount++;
            }
            this._partyAffiliation = newVal.toLowerCase();
        }
    }
});

Object.defineProperty(filterObj, "filterCount", {
    enumerable: true,
    get(){
        return this._filterCount;
    },
    set(newVal) {
        
        this._filterCount = newVal;
    }
});

export default filterObj;

It’s implemented like this for 2 main reasons:

  1. I always want the value being passed in to be in lowercase and its easy to do that within the setter for presidentName.
  2. I want be be able to tell how many filter criteria Im dealing with when I go to actually apply the filter. The reason for this is that if Im dealing with only 1 filter, I’m essentially doing a logical OR whereas if I have 2 criteria, Im performing a logical AND.
  3. Its just nice and easy to pass an object with all the info I need

Whenever the user interacts with the Filter component controls in the UI, it is all kept in sync with the filterObj:

<template>
    <div>
        <div class="box">filter presidents <br>
            <label for="name">name: </label>
            <input type="text" class="input" v-model="filterObj.presidentName" @keyup="filterPresidents">
            
            <label for="name">party: </label>
            <select class="input" @change="filterPresidents" v-model="filterObj.partyAffiliation"> 
                <option  v-for="(party, key) in parties" :value="party" :key="key">{{ party }}</option>
            </select>
        </div>
    </div>
</template>

<script>
    import filterObj from "../utils/filterObj";
    export default {
        name: 'FilterPanel',
        data() {
            return {
                parties: ['','republican', 'democrat', 
                          'federalist', 'whig',       
                          'democratic-republican', 'none'],
                filterObj
            }
        },
        methods: {
            filterPresidents() {
                this.$store.dispatch('filterPresidents', 
                this.filterObj);
            }
        }
    }
</script>

We call the filterPresidents() method on the component whenever the data in either control changes.

If you’ve worked with Vuex, you know that we never modify the state directly. Instead, we dispatch to Actions on the Store which we do from within the filterPresidents() method passing along our filter object:

filterPresidents() {
       this.$store.dispatch('filterPresidents', 
       this.filterObj);
}

Once “inside” the store, these Actions then Commit mutations which mutate the state in some way:

const state = {
    presidents: [],
    filterObj: {}
};

const actions = {
    filterPresidents({commit}, filterObj) {
        commit('UPDATE_FILTER', filterObj);

    }
};

const mutations = {
    UPDATE_FILTER(state, payload) {
        state.filterObj = payload;
    }
};
const getters = {
    presidents: state => state.presidents
}

With all that in place, all that’s left to do it update our getter from the simple one shown above, to one that recognizes that we now have a filterObj on the state and we can use it to do dynamic filtering:

const getters = {    
    presidents: state => {
        switch(state.filterObj.filterCount) {
            //no filters passed
            case 0:
            case undefined:
                return state.presidents;

            //only 1 filter passed
            case 1:
                return state.presidents.filter(president => {
                    for(let key in state.filterObj) {
                        if(president[key]) {
                       if(president[key].toLowerCase().includes(state.filterObj[key]))
                                return true
                        }
                    }
                    return false;
                });

            //2 filters passed
            case 2:
                return state.presidents.filter(president => {
                    for(let key in state.filterObj) {
                        if(president[key]) {
                            if(!president[key].toLowerCase().includes(state.filterObj[key]))
                                return false;
                        }
                    }
                    return true;
                });
        }
    }
};

Now we can easily filter based on the number of “filters” passed to the store.

filtering data in Vuex

There are probably ways to make this more efficient but its a work in progress. By all means, please post a comment if you have recommendations for improvements of modifications.

In the mean time, thanks for visiting and happy Vueing!

Vue render function basics

If you’re using the Vue CLI to scaffold you VueJS projects (and if you aren’t, you should be) you’ve surely noticed that when you create a new project it will generate a number of files for you. One of these being main.js.

If you open up main.js you will find something similar to the following:

import Vue from 'vue';
import App from './app/App.vue';

new Vue({
  render: h => h(App)
}).$mount('#app');

After having opened and gazed upon said file, you may be wondering to yourself…what the heck is that render thingy…and what about that h?

Lets take this apart piece by piece

On the options object that we’re passing to the Vue constructor function, we have a function called render. Its written above as an ES6 arrow function but we could also write it as: 

new Vue({
    render: function(h) {
        return h(App);
    }
}).$mount('#app');

To that render function we are passing the argument: h

h is short-hand for createElement() in HyperScript.  HyperScript is a language that creates HTML. (read more about HyperScript here)  So, if we expand the example above even more we get:

new Vue({
    render: function(createElement) {
        return createElement(App);
    }
}).$mount('#app');

If you have been working on the web for any period of time, this createElement() method should look very familiar. If you want to review its functionality, check out the MDN docs here.

To the createElement method, we are passing a reference to the App component that we imported at the top of the file.

import App from './app/App.vue';

A .vue file is what is known as a SFC or Single File Component. The browser has no idea what this file is but thats ok because during the build process it is parsed with the vue-loader plugin  and inserted into the DOM using the $mount() function exposed on the root Vue instance. You can read more about that here.

Turning our attention back to the render function, in this instance we are passing a Single File Component to it to be rendered but we dont have to do that. What if we just wanted to create a simple DIV and have it inserted into the DOM? We could do something like this:

new Vue({
  render(createElement) {
    return createElement('div', 'hello from my newly created div');
  }
}).$mount("#app");

which would give me:

Now, granted, this isnt a real life example of what you can do with the render function but it does show you to power it can provide.

Most of the time, templates will be sufficient for our application development needs but at those times where we may need a little more flexibility and power, it nice to know that we can drop down to the render function and get the full power of JavaScript.

Thanks for visiting.

Solving the “You are using the run-time only build of Vue” error

So you downloaded all the latest bits of Vue and you’re ready to get started building the next Amazon.com. Just to be sure you’re on the right track, you fire up your development server and browse to localhost:8080 and….nothing. After a couple of chin scratches and an audible groan you pop open dev tools in chrome and are met with the following error…

You are using the runtime-only build of Vue where the template compiler is not available.

You collapse back into your carefully chosen desk chair (good lumbar support is important ya know), your Amazon dreams smashed upon the rocks.

What’s happening you wonder.

When I started working with Vue I ran into this very error. I googled around with DuckDuckGo and found a few tips and discussions regarding this problem but none that really covered all the bases..so I thought I would write my own.

Depending on how you create your application dictates whether you will be using a run-time only version of Vue or a compiler + runtime version. The difference between the two is that, well, the compiler + runtime version includes the compiler while the other version does not.  This seemingly obvious statement may have you wondering, OK…so what.

If you intend to use inline templates you must use the compiler + runtime version of the library. Lets look at an example:

Here is a super simple HTML page that links to the Vue library on a CDN:

<!DOCTYPE html>
<html>
<head>
  <title>compiler versions</title>
</head>
<body>
  <div id="app">
    <input class="input" type="text" v-model="input" />
    <br>
    <p>{{ input }}</p>
  </div>

  <script src="https://unpkg.com/vue"></script>
  <script>
    new Vue({
        el: '#app',
        data: {
          input: ''
        }
    })
  </script>
</body>

</html>

When you link to the Vue library as shown above, you get the compiler + runtime version of the library and the page above will happily display in a browser.

If, however, you were to use the Vue CLI to create your project, it automatically references the runtime only version of the library. That’s all well and good until you decide to reference the root html node from your Vue instance like so: 

new Vue({
        el: '#app',
        data: {
          input: ''
        }
    })

and maybe you decided you would like to inline a template:

new Vue({
    el: '#app',
    data: {
        input: ''
    },
    template: 
`
    <input type="text" v-model="input" >
`
})

In both of these cases you need to have the compiler + runtime version of Vue or you will get the dreaded:

You are using the runtime-only build of Vue where the template compiler is not available.

So, what is one to do? Well, as we saw above, if you just linked to the CDN, you’re all set. Nothing more to worry about…..it’s Miller time!

If, however, you created your project using the Vue CLI  , you have a little more configuration ahead of you. Its not much and you will be covered if you suddenly have an overwhelming desire to inline a template. OK…so maybe its just a casual desire but roll with me here.

So what do we do? Well, dear reader, it really depends on which version of the Vue CLI you used to create your project. Let’s look at each.

The latest Vue CLI – version 3 (as of Sept 2018)

In order to get the compiler + runtime version of Vue in this scenario, you need to add an additional config file to your project root. This files is called vue.config.js. You can read all about this file and its options here.

In this nifty new file you need to add the following code (as shown in the docs here) :

module.exports = {
    runtimeCompiler: true
}

Versions 1 & 2 of the Vue CLI

Earlier versions of the projects created by the CLI use the webpack.config.js file to configure Webpack. In order to get your project to run with the compiler + runtime, you need to edit the webpack.config.js file. (Check out all the cool stuff WebPack can do here) Within the resolve section, you need to add the following config:

 resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },

Notice how this alias references the vue/dist/vue.esm.js file. That is the one you want.  You can read all the details about the different builds here.

And there you have it. Now you’re on your way to building awesome sites with Vue and using that spiffy compiler whenever you like.

You do want to be sure you dont run with the compiler in production as the runtime-only build is about 30% lighter.

Happy Viewing…uh….Vueing?….You get the idea.

Thanks for visiting!