Search code examples
loopsvue.jsvuejs2componentsv-model

v-model in v-loop appears in text-field but nothing changes


i'm trying to loop through array of objects to make a list of input field, everything works fine except v-model, it just appears in the text-field but nothing changed, i've seen a lot of similar questions here but none of the answers worked

my root component:

<template>
<div class="container">
    <add-product :inputs="inputs"></add-product>>
</div>
</template>

<script>

export default {
data(){
    return{
        inputs: [
            {id: '1', label: 'name', type: 'text', model:'name'},
            {id: '2', label: 'description', type: 'text', model: 'desc'},
            {id: '3', label: 'price', type: 'text', model: 'price'},
            {id: '4', label: 'size', type: 'text', model: 'size'},
            {id: '5', label: 'stock', type: 'text', model: 'stock'},

        ]
    }
},
components: {
    addProduct: AddProduct
}
}

Nested component:

 <template>
<transition name="fade">
    <div class="add-box">
        <div class="input--form" v-for="(input, index) in inputs" :key="index">
            <label >{{ input.label }}: </label>
            <input :type="input.type" v-model="input.model">
        </div>
    </div>
</transition>    
</template>

<script>
 export default {
props: {
    inputs: Array
},
data(){
    return{
        name: '',
        desc: '',
        price: '',
        size: '',
        stock: ''
    }
},

  </script>

Solution

  • The main reason nothing happens is because you don't emit an event called input from the child component. v-model is just a shortcut for binding a value and emitting an event, thus implementing the two-way data binding with a single directive (see here).

    However, you shouldn't try to directly modify the prop in the children components. It is a highly recommended practice to not modify any value without the parent knowing about it.

    In general, whenever you need to iterate over values, rather than making a component that handles the whole array, it is often better to have a component handle a single value out of the array. The way you named the child component, AddProduct, suggests that you also wanted to it like this in a way. So I would suggest the following modifications.

    Nested component

     <template>
      <transition name="fade">
        <div class="add-box">
          <div class="input--form">
            <label>{{ input.label }}: </label>
            <input :type="input.type" :value="input.model" @input="onInput" />
          </div>
        </div>
      </transition>
    </template>
    
    <script>
    export default {
      props: {
        input: Object,
      },
      methods: {
        onInput(event) {
          this.$emit("input", {
            ...this.input,
            model: event.target.value,
          });
        },
      },
    };
    </script>
    

    Parent component

    <template>
      <div id="app">
        <add-product
          v-for="(input, index) in inputs"
          :key="input.id"
          :input="input"
          v-model="inputs[index]"
        ></add-product>
      </div>
    </template>
    
    <script>
    import AddProduct from "./AddProduct";
    
    export default {
      name: "App",
      components: {
        AddProduct,
      },
      data() {
        return {
          inputs: [
            { id: "1", label: "name", type: "text", model: "name" },
            { id: "2", label: "description", type: "text", model: "desc" },
            { id: "3", label: "price", type: "text", model: "price" },
            { id: "4", label: "size", type: "text", model: "size" },
            { id: "5", label: "stock", type: "text", model: "stock" },
          ],
        };
      },
    };
    </script>
    

    The child component doesn't modify the prop, only emits the input object with updated value. Since the name of the event is input and the prop's name is value, v-model will work. In the parent the inputs array is iterated and for each input the v-model directive is attached. Find a live demo here