Designing reusable form field components (using VueX)


#1

To design a reusable form field component you need to have some common way to bind this component to VueX property that holds a value of the field. Here’s quick how-to.
Let’s say you have a state in VueX store like this:

state: {
    forms: {
        registrationForm: {
            email: ''
        }
    }
}

So you create, for example, and input field component like this:

<template>
    <input :type="fieldType" :value="getFieldValue" @change="onFormFieldValueChange"/>
</template>
<script>
    import { mapActions } from 'vuex';

    export default {
        name: 'InputField',
        props: {
            fieldType: String,
            targetFieldPath: String
        },
        computed: {
            getFieldValue() {
                const targetFieldPathList = this.targetFieldPath.split('.');
                let currentProp = this.$store.state.forms;

                targetFieldPathList.forEach((pathNode) => {
                    currentProp = currentProp[pathNode];
                });

                return currentProp;
            }
        },
        methods: {
            ...mapActions([
                'setFormFieldValue'
            ]),
            onFormFieldValueChange(event) {
                // validation can also be added here
                this.setFormFieldValue({
                    targetFieldPath: this.targetFieldPath,
                    value: event.target.value
                });
            }
        }
    };

And the last part, action and mutation in the VueX store:

...
mutations: {
        // unified way to set properties in 'state.forms' Observer object
        setFormFieldValue(state, { targetFieldPath, value }) {
            const targetFieldPathList = targetFieldPath.split('.');
            const targetFieldPathIndex = targetFieldPathList.length - 1;
            let currentProp = state.forms;

            // traversing Observer object properties until we get to the target property parent
            for (let pathNodeIndex = 0; pathNodeIndex < targetFieldPathIndex; pathNodeIndex++) {
                currentProp = currentProp[targetFieldPathList[pathNodeIndex]];
            }

            currentProp[targetFieldPathList[targetFieldPathIndex]] = value;
        }
    },
    actions: {
        setFormFieldValue({ commit }, { targetFieldPath, value }) {
            commit('setFormFieldValue', { targetFieldPath, value });
        }
    }
...

Now you have a reusable form field component that is easily 2-way binded to any property in VueX store namespace for storing form field values.

<InputField fieldType="text" targetFieldPath="registrationForm.email"/>