Search code examples
vue.jsvue-componentv-model

VueJS - How to use v-model in the right way when using child components


Well I'm kind of new to VueJS.. right now I'm learning the way that components work.

I got confused when I tried to update a data parameter of parent that was passed to a child component that is used as a v-model within the child.

I'll add an example so you understand what I'm talking about(hard for me to explain myself)

Note: I used an array as a v-model because I need a way to iterate through all of the checkboxes v-model and update them later on

the example im about to show is written on the go. so it's not the actual code but you can get the concept from it.

Let's say we have the template of :

<script type="x-template id="tree">
    <table>
        <thead>
            <tr>
                <th>Tree-Item</th>
                <th>check chilld</th>
            </tr>
        </thead>
        <tbody>
            <template v-for="child in item.childrens">
                <tr v-if="child.childrens.length <= 0">
                    <td>{{child.name}}</td>
                    <td>
                        <input type="checkbox" v-model="checkboxes[child.id].value" :value="1" />
                    </td>
                </tr>
                <tr v-else>
                    <td colspan="2">
                        <tree :item="child" :checkboxes="checkboxes"></tree>
                    </td>
                </tr>
            </template>
        </tbody>
    </table>
</script>

the component:

    Vue.component("tree", {
        template: "#tree",
        props: {
            item: Object,
            checkboxes: []
        }
    }

the vue object

    var mainVue = new Vue({
        el: "#app",
        data: {
            treeData: {
                id: 0,
                name: "a name",
                childrens: [
                    {
                        id: 1,
                        name: "b name",
                        childrens: []
                    }, {
                        id: 2
                        name: "c name"
                        childrens: [
                            {
                                id: 3,
                                name: "d name",
                                childrens: []
                            },
                            {
                                id: 4,
                                name: "d name",
                                childrens: []
                            },
                            {
                                id: 5,
                                name: "d name",
                                childrens: []
                            }
                        ]
                    }
                ]
            },
            checkboxes: []
        }, mounted: function() {
            //looping all the childrens... setting checkboxes array to contain data for each index of child
            // checkboxes array now contains the objects of - [{value: 0},{value: 0},{value: 0},{value: 0},{value: 0},{value: 0}]
        }
    });

my app will look like:

    <div id="app">
        <tree :item="treeData" :checkboxes="checkboxes"></tree>
    </div>

now it will work. if I'll change one of the values of the array in the mounted function to 1, it will be set as checked. but my problem is that I want to update it also in real time.

so if i'll have a button lets say:

    <div id="app">
        <tree :item="treeData" :checkboxes="checkboxes"></tree>
        <button @click="changeCheckboxes">a button</button>
    </div>

and I'll add a method to change the "checkboxes" array to something new:

changeCheckboxes: function() {
    this.checkboxes = [{value: 1}, {value: 0}, {value: 1}, {value: 0}, {value: 1}, {value: 0}];
}

it will not update the component and I'll see no effect.. even if i'll use the this.$forceUpdate()

so after I got you briefed in the details. is there any option to update a v-model within a child component directly from the root vue app?

Thanks for your time! hope we will figure it out :)


Solution

  • https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays

    Vue cannot detect the following changes to an array:

    1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
    2. When you modify the length of the array, e.g. vm.items.length = newLength

    The problem is that you are doing v-model="checkboxes[child.id].value". V-model is really just a fancy way of doing:

     <input type="checkbox" :value="checkboxes[child.id].value" @input="checkboxes[child.id].value = $event"
    

    The @input part in your v-model is what Vue cannot detect. I think for this issue if you need the checkboxes and items separated, and you don't want to rewrite a lot, you have to ditch v-model and use value and @input separately:

     <input type="checkbox" :value="checkboxes[child.id].value" @input="toggleCheckbox(child.id)"
    
    methods: {
      toggleCheckbox(index) {
       this.checkboxes.splice(index, 1, !this.checkboxes[index].value)
      }
    }
    

    this being if I understood you correctly that child.id corelates to index in checkboxes array.

    HOWEVER

    I see that you are in fact mutating your props, which in Vue is a bad practice. You should use the prop/emit workflow. This means that the parent should always be responsible for data changes.

    So the best way would be to change the toggleCheckbox funxtion above to emit the change up and then let the parent mutate the checkboxes array. Once the parent does this, the child will automatically change too.