Vue.js - emit to update array not working
Asked Answered
T

2

13

I have two components: Parent and Child. The Parent has an array of cars the Child is supposed push objects the cars array. My problem is that my Child component turns cars into an object, instead of pushing an object into the cars array. My Parent component:

<template>
   <child v-model="cars"></child>
    <ul>
      <li v-for="car in cars">
         {{ car.model }}
      </li>
    </ul>
</template>

export default {
 data: function() {
  return {
    cars: []
  }
 }
}

My Child component:

<template>
    <div>
        <button type="button" @click="addCar()">Add Car</button>
    </div>
</template>

export default {
    methods: {
        addCar() {
            this.$emit("input", { model: "some car model" })
        }
    }
}

Expected results:

cars gets updated and becomes [{ model: "some car model"}, { model: "some car model"}, etc...]

Actual results:

cars becomes an object {model: "some car model"}

Here is my fiddle:

https://jsfiddle.net/t121ufk5/529/

I assume something is wrong with the way i am using v-model on my child component and/or the way I am emitting is incorrect. Can someone help? Thanks in advance!

Tait answered 5/8, 2018 at 13:34 Comment(0)
C
22

Lets discuss, why you not get proper result.Then we discuss other approach to solve this problem.

Firstly we need to understand how v-model works on custom components by default.

When using a text input (including types such as email, number, etc.) or textarea, v-model="varName" is equivalent to :value="varName" @input="e => varName = e.target.value". This means that the value of the input is set to varName after each update to the input varName is updated to the value of the input. A normal select element will act like this too, though a multiple select will be different.

Now we need to understand,

How Does v-model Work On Components?

Since Vue doesn’t know how your component is supposed to work, or if it’s trying to act as a replacement for a certain type of input, it treats all components the same with regards to v-model. It actually works the exact same way as it does for text inputs, except that in the event handler, it doesn’t expect an event object to be passed to it, rather it expects the value to be passed straight to it. So…

<my-custom-component v-model="myProperty" />

…is the same thing as…

<my-custom-component :value="myProperty" @input="val => myProperty = val" />

So when you apply this approach. You have to receive value as a props. and make sure you $emit name is input.

Now you can ask me at this stage,what you do wrong?

Ok, look at like code @input="val => myProperty = val"

when you $emit whit a new value. this newValue will updated our parent value which you wanna update.

Here is your code this.$emit("input", { model: "some car model" }).

You update your parent value with a object. So your Array updated with a Object.

Lets solve the full problem.

Parent Component: `

<template>
   <child v-model="cars"></child>
    <ul>
      <li v-for="car in cars">
         {{ car.model }}
      </li>
    </ul>
</template>

export default {
 data: function() {
  return {
    cars: []
  }
 }
}

`

Child Component:

<template>
    <div>
        <button type="button" @click="addCar()">Add Car</button>
    </div>
</template>

export default {
    props: ['value']
    methods: {
        addCar() {
            this.$emit("input", this.value.concat({model: "some car model"}))
        }
    }
}

You can actually solved it several way.

Second Approach,

Parent:

<template>
   <child :cars="cars"></child>
    <ul>
      <li v-for="car in cars">
         {{ car.model }}
      </li>
    </ul>
</template>

export default {
 data: function() {
  return {
    cars: []
  }
 }
}

Child:

<template>
    <div>
        <button type="button" @click="addCar">Add Car</button>
    </div>
</template>

export default {
    props: {
       cars: {
          type: Array,
          default:[]
       }
    },
    methods: {
        addCar() {
            this.cars.push({ model: "some car model" })
        }
    }
}

Last Approach:

Parent:

    <template>
        <child @update="addCar"></child>
            <ul>
                <li v-for="car in cars">
                    {{ car.model }}
                </li>
            </ul>
    </template>

    export default {
        data() {
            return {
                cars: []
            }
        }
   },
   methods: {
      addCar() {
           this.cars.push({ model: "some car model" })
      }
   }
}

Child:

 <template>
     <div>
        <button type="button" @click="update">Add Car</button>
     </div>
    </template>

    export default {
        methods: {
            update() {
                this.$emit('update')
            }
        }
    }
Cryo answered 5/8, 2018 at 17:51 Comment(1)
Regarding the first few solutions, the latest Vue docs say that data flow for properties should be one-way, from parent down to child. So emitting values in custom events seems to be the recommended approach here. (v3.vuejs.org/guide/component-props.html#one-way-data-flow)Hamlin
N
3

It is possible to trigger the event of updating the transmitted value in props

In the parent

<my-component :is-open.sync="isOpen" />

In my-component

this.$emit('update:isOpen', true)
Narcis answered 3/12, 2020 at 11:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.