1

Update

Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf

Problem:

I have a vue component that looks like this:

sub-comp.vue

<template>
    <div>
        <input type="text" class="form-control" v-model="textA">
        <input type="text" class="form-control" v-model="textB">
        <input type="text" class="form-control" v-model="textC">
    </div>
</template>
<script>
    export default {
        props: {
            textA: {
                type: Number,
                required: false
            },
            textB: {
                type: Number,
                required: false
            },
            textC: {
                type: Number,
                required: false
            }
        }
    }
</script>

I have a parent component that looks like this:

layout-comp.vue

<template>
    <div>
        <button @click="addItem">Add</button>
        <ul>
            <li v-for="listItem in listItems"
                :key="listItem.id">
                <sub-comp 
                      :textA="listItem.item.textA"
                      :textB="listItem.item.textB"
                      :textC="listItem.item.textC"
                />
            </li>
        </ul>
    </div>
</template>
import subComp from '../sub-comp.vue'
export default {
    components: {
        subComp
    },
    data() {
        return {
            listItems: []
        }
    },
    methods: {
        addItem: function () {
            var item = {
                         textA: 5,
                         textB: 100,
                         textC: 200
                       }
            if (!item) {
                return
            }
            this.length += 1;
            this.listItems.push({
                id: length++,
                item: item
            });
        }
    } 
</script>

The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as

 { 
     textA: 5,
     textB: 100, 
     textC: 200 
 }

Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:

https://v2.vuejs.org/v2/guide/list.html#Caveats

Due to limitations in JavaScript, Vue cannot detect the following changes to an array

Question:

I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the @blur event. I'd like to see what ways this can be done.

I read these materials:

https://codingexplained.com/coding/front-end/vue-js/array-change-detection https://v2.vuejs.org/v2/guide/list.html

But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.


Update 4/12/2018

Found out that in my addItem() that I had:

item = this.conditionItems[this.conditionItems.length - 1].item);

to

item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));

I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:

https://github.com/vuejs/Discussion/issues/292

1 Answer 1

2

The problem is that props flow in one direction, from parent to child.

Setting the value using v-model in child won't affect parent's data.

Vue has a shortcut to update parent's data more easily. It's called .sync modifier.

Here's how.

In sub-comp.vue

<template>
    <div>
        <input type="text" class="form-control" :value="textA" @input="$emit('update:textA', $event.target.value)" >
        <input type="text" class="form-control" :value="textB" @input="$emit('update:textB', $event.target.value)">
        <input type="text" class="form-control" :value="textC" @input="$emit('update:textC', $event.target.value)">
    </div>
</template>

<script>
export default {
  // remains the same
}
</script>

add .sync when you add the props

             <sub-comp 
                  :textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
                  :textB.sync="listItem.item.textB"
                  :textC.sync="listItem.item.textC"
            />

update:

if you have reactivity problem, don't use .sync, add a custom event and use $set

             <sub-comp 
                  :textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
            />
Sign up to request clarification or add additional context in comments.

1 Comment

Wow thank you for pointing me to this. I got certain parts of it working, I will mark as answered if I don't run into any hiccups.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.