HomeVueJSVuex Reduce Boilerplate using vuex-map-fields

Vuex Reduce Boilerplate using vuex-map-fields

Vuex is a great tool for state management, but i find that using vuex causes lot of boilerplate code. I will try to explain it in this blog post and will explore a tool called “vuex-map-fields” which i think makes things simpler.

Before going further, its important to realize that vuex-map-fields is only useful for two way data binding of form fields.

Let’s first take a simple example of vuex with two text fields

here is how the template looks

<template>
  <div class="hello">
    <h1>Text1: {{ text1 }}</h1>

    <div>
      <input type="text" :value="text1" @input="updateText1"/>
    </div>

    <br/>
    =======================
    <br/>

    <h1>Text2: {{ text2 }}</h1>

    <div>
      <input type="text" v-model="text2"/>
    </div>
  </div>
</template>

Just simple input type with v-model and its binded to text1, text2.

I have taken two different appraoches on this, one is using v-model and other using :value and @input just so that we can both the methods.

Next this is how the store looks,

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    text1: "",
    text2: ""
  },
  mutations: {
    setText1(state, text1){
      state.text1 = text1;
    },
    setText2(state, text2){
      state.text2 = text2;
    }
  },
  actions: {
    
  }
})

again very simple. this has state and mutations thats all.

and finally this is how the components looks,

<script>
import { mapState } from 'vuex'
export default {
  name: "HelloWorld",
  computed: {
    ...mapState({
      text1 : state => state.text1
    }),
    text2: {
      get () {
        return this.$store.state.text2;
      },
      set (value) {
        this.$store.commit('setText2', value)
      }
    }
  },
  methods: {
    updateText1(e) {
      this.$store.commit("setText1", e.target.value);
    }
  }
};
</script>

So above what we can see is, just for two state variables text1, text2 there is so much boilerplate like

  • text1 : state => state.text1
  • this.$store.commit(‘setText2’, value)
  • this.$store.commit(“setText1”, e.target.value);

etc..

When i started with vuex, i found this very frustrating. There is just too much code to write and i found this solution https://github.com/maoberlehner/vuex-map-fields

Let’s see how the above same code will be written with vuex-map-fields

New template looks like this

<template>
  <div class="hello">
    <h1>Text1: {{ text1 }}</h1>

    <div>
      <input type="text" v-model="text1"/>
    </div>

    <br/>
    =======================
    <br/>

    <h1>Text2: {{ text2 }}</h1>

    <div>
      <input type="text" v-model="text2"/>
    </div>
  </div>
</template>

Not much change here, just that using v-model for both the fields.

 

Now lets look at the store

import Vue from 'vue'
import Vuex from 'vuex'

import { getField, updateField } from 'vuex-map-fields';


Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    text1: "",
    text2: ""
  },
  getters: {
    getField
  },
  mutations: {
    updateField
  },
  actions: {
    
  }
})

This is so simple, all code is gone and this looks much simpler.

Let’s look at our component now

<script>
import { mapState } from 'vuex'
import { mapFields } from 'vuex-map-fields'
export default {
  name: "HelloWorld",
  computed: {
    ...mapFields([
      "text1",
      "text2"
    ])
  }
};
</script>

This also looks much simpler, most of the code is gone.

This looks much better!!

Next, let’s further explore how code will look if we have two different components and two stores.

 

When we have two components and two stores with vuex-map-fields we start to get the error below

[vuex] duplicate getter key: getField

to fix this we have to use https://github.com/maoberlehner/vuex-map-fields#custom-getters-and-mutations

lets see how code looks with this

first lets look at our stores and since now we have 2 stores so the main store.js looks like this

src/store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import Comp1 from "@/store/comp1"
import Comp2 from "@/store/comp2"


export default new Vuex.Store({
  modules: {
    a: Comp1,
    b: Comp2
  }
})

next our two stores for each components

src/stores/comp1.js

import { getField, updateField } from 'vuex-map-fields';

export default {
  state: {
    text1: "",
    text2: ""
  },
  getters: {
    getComponent1(state){
        return getField(state);
    }
  },
  mutations: {
    updateComponent1(state, field){
        return updateField(state, field);
    }
  },
  actions: {
    
  }
}

as you can see we have used customer getters, mutation function which in-turn calls  getField()

it’s explained in further detail on there read me page.

src/store/comp2.js

import { getField, updateField } from 'vuex-map-fields';

export default {
    state: {
        text4: "",
        text5: ""
    },
    getters: {
        getComp2Field(state) {
            return getField(state);
        }
    },
    mutations: {
        updateComp2Field(state, field) {
            return updateField(state, field)
        }
    },
    actions: {

    }
}

and this is how our component looks

src/components/Comp1.vue

<template>
  <div class="hello">
    <h1>Text1: {{ text1 }}</h1>

    <div>
      <input type="text" v-model="text1"/>
    </div>

    <br/>
    =======================
    <br/>

    <h1>Text2: {{ text2 }}</h1>

    <div>
      <input type="text2" v-model="text2"/>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'

import { createHelpers } from 'vuex-map-fields';
const { mapFields } = createHelpers({
  getterType: 'getComponent1',
  mutationType: 'updateComponent1',
});

export default {
  name: "Comp1",
  computed: {
    ...mapFields(["text2", "text1"])
  }
};
</script>

src/components/Comp2.vue

<template>
  <div class="hello">
    <h1>Text4: {{ text4 }}</h1>

    <div>
      <input type="text" v-model="text4"/>
    </div>

    <br/>
    =======================
    <br/>

    <h1>Text5: {{ text5 }}</h1>

    <div>
      <input type="text" v-model="text5"/>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
import { createHelpers } from 'vuex-map-fields';

const { mapFields  } = createHelpers({
    getterType: 'getComp2Field',
    mutationType: 'updateComp2Field',
  });

export default {
  name: "Comp2",
  computed: {
    ...mapFields(["text4","text5"])
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

This way we are able to use vuex-map-fields in multiple components.

 

The problem current with vuex-map-fields is that, we try to use for complex state operations it not helpful.  This help only with setters/getters hence can’t be used much in projects. 

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: