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. Depending on your situation, this may or may not be applicable. For me, it worked out just fine. 

The Setup

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>

You can see in the code above that when the created() hook is triggered, the getPresidents() action is invoked 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;
    }
};

In the getPresidents() action, we use axios to make an HTTP call and load the data from the server. The data is then 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. 

Let’s 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;

I implemented it like this for 3 main reasons:

  1. I always want the value being passed in to be lowercase and it’s easy to do that within the setter for presidentName.
  2. I want to be able to tell how many filter criteria I’m dealing with when I apply the filter. The reason for this is that if I’m dealing with only 1 filter, I’m essentially doing a logical OR whereas if I have 2 criteria, I’m performing a logical AND.
  3. It’s 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. We can do this in the filterPresidents() method passing along our filter object:

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

Once “inside” the store, the Actions 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 that in place, all that’s left to do is 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!