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.