Reputation: 1037
I've been learning vue for a few days now and I'm trying out passing data/props between child and parent. Now I have the following child:
<template>
<div>
<input v-model="name1" placeholder="string">
<input v-model="number1" placeholder="number">
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And then parent:
<template>
<div>
<child/>
</div>
</template>
<script>
import child from "@/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
}
}
</script>
Now when I enter some text into the input fields, it displays correctly in the paragraphs, since the props bound to the paragraphs have changed. However, I get this warning:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "name1"
found in
---> <Child>
<Parent> at src/components/complexComponent4/parent.vue
<MyComplexView4.vue> at src/views/myComplexView4.vue
<App> at src/App.vue
<Root>
I read about this error in multiple places on the internet and also in the documentation, and I found that mutating props is deemed an anti-pattern now: https://michaelnthiessen.com/avoid-mutating-prop-directly
Unfortunately I didn't really find anything specific and/or helpful on how to deal with this problem. Especially in context of vue handling primitive data and objects/arrays differently (objects/arrays are passed by reference).
v-model seems to play an important role in leveraging the power of vue, since it enables two-way binds. Therefore I wouldn't want to omit it entirely, unless its use has become so difficult that it doesnt justify the gains.
Upvotes: 3
Views: 8645
Reputation: 324
A guideline for vue.js is that you can use props to automatically alter data in the child from the parent, but not vice-versa. For altering data of the parent-component, the child-component is supposed to use events. You could consider using two different components for name1 and number1 respectively and bind the values in a two-way-manner by making these components applicable for v-model
, as it is described here.
Upvotes: 0
Reputation: 922
As the warning says, you should avoid mutating props directly in child component. So you should emit an event from child to parent to let parent know that prop value had been changed. Parent will change the prop and pass it down to child.
For such purpose there is a syntactic sugar in Vue called .sync modifier.
So your components could be something like this. Child:
<template>
<div>
<input
:value="name1"
@change="$emit('update:name1', $event.target.value)"
placeholder="string"
/>
<input
:value="number1"
@change="$emit('update:number1', $event.target.value)"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And parent:
<template>
<div>
<child :name1.sync="name1" :number1.sync="number1"/>
</div>
</template>
<script>
import child from "@/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
},
data() {
return {
name1: '',
number1: ''
}
}
}
</script>
Or for more complicated cases you can either use v-model and computed properties with setters in child component:
<template>
<div>
<input
v-model="computedName1"
placeholder="string"
/>
<input
v-model="computedNumber1"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
},
computed: {
computedName1: {
get() { return this.name1 },
set(value) {
// some logic
this.$emit('update:name1', value)
},
computedNumber1: {
get() { return this.number1 },
set(value) {
// some logic
this.$emit('update:number1', value)
}
}
}
}
</script>
Upvotes: 10
Reputation: 699
If you intend to change prop passed down to child assign it first to child data.
<template>
<div>
<input v-model="name" placeholder="string">
<input v-model="number" placeholder="number">
<p v-text="name"></p>
<p v-text="number"></p>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
name: null,
number: null
}
},
props: {
name1 : String,
number1 : Number
},
mounted() {
this.name = this.name1;
this.number = this.number1;
}
}
</script>
When data is changed you can $emit those changes to parent component
Parent
<child :number1.sync="number1" :name1.sync="name1" />
Child
watch: {
name: value => this.$emit('update:name1', value)
number : value => this.$emit('update:number1', value)
},
Parent
<child :number1="number1" :name1="name1" @changeNumber="value => number1 = value" @changeName="value => name1 = value" />
Child
watch: {
name: value => this.$emit('changeName', value)
number : value => this.$emit('updateNumber', value)
},
Upvotes: 2