I have an object in my component data. Now, I'm just binding all the properties of the object as a prop to the child component using v-bind.sync
directive. I'm updating these props from the child component using the built-in update
event but still, I'm getting Avoid mutation props directly
error in the console. Here is the minimal example attached.
Parent Component
<template>
<div>
<oslo v-bind.sync="data" />
</div>
</template>
<script>
import Oslo from '@/components/Oslo.vue'
export default {
components: {
Oslo,
},
name: 'OsloParent',
data() {
return {
data: {
data: {
name: 'Oslo name',
access: 'admin'
}
},
}
},
}
</script>
Child component
<template>
<div>
<input type="text" v-model="name" @keyup="$emit('update:name', name)" />
<input type="text" v-model="access" @keyup="$emit('update:access', access)" />
</div>
</template>
<script>
export default {
props: {
name: String,
access: String
},
name: 'Oslo',
}
</script>
This is just an example component I've created for the reproduction of the problem. The actual component is supposed to handle so many props with two-way binding and that's the reason I'm binding the data with v-bind
directive with sync
modifier. Here is the Vue warning from the console (most common).
[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: "name"
Any suggestions to improve this or silent the Vue warn for this specific case? The above-given components works as desired, Vue throws error though.
CodePudding user response:
Try like following:
Vue.component('Oslo', {
template: `
<div>
<input type="text" v-model="namec" @keyup="$emit('updated', {dat:namec, id: 'name'})" />
<input type="text" v-model="accessc" @keyup="$emit('updated', {dat:accessc, id: ''})" />
</div>
`,
props: {
name: String,
access: String
},
data() {
return {
namec: this.name,
accessc: this.access
}
}
})
new Vue({
el: '#demo',
data() {
return {
name: 'Oslo name',
access: 'admin'
}
},
methods: {
updated(data){
data.id === 'name' ? this.name= data.dat : this.access= data.dat
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div>
{{name}} {{access}}
<oslo @updated="updated" />
</div>
</div>
CodePudding user response:
You can just pass an object and sync it instead of individual properties if you have many properties to listen to from child component. See the example below:
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.component('Oslo', {
template: `
<div>
<input type="text" v-model="comp_name" @keyup="$emit('update:name', comp_name)" />
<input type="text" v-model="comp_access" @keyup="$emit('update:access', comp_access)" />
</div>
`,
props: {
data: {
name: String,
access: String,
}
},
data() {
return {
comp_name: this.data.name,
comp_access: this.data.access
}
}
})
new Vue({
el: '#app',
data() {
return {
doc: {
name: 'Oslo name',
access: 'admin'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<span>---- {{ this.doc.name }}----</span>
<span>---- {{ this.doc.access }}----</span>
<oslo :data="this.doc" v-bind.sync="doc" />
</div>
</div>
CodePudding user response:
I found two problems with your example that might throw this off.
The use of
v-model
directly to the property. Usev-bind
instead to have it only display. And usev-on:change
handler to fire the$emit('update:propertyname', value)
and send the new value to update on the object.The value sent along in the
$emit
seems empty and thus makes no change. Use$event.target.value
instead.
Side note: v-on:keyup
might not be the best event to listen to, since input can also be drag-and-dropped. Listening to v-on:change
would be beter in that case.
Note on event listeners when using only v-bind.sync
instead of v-bind:propertyName.sync
:
If you want to listen to the update:propertyName
event from the child component on the parent, you have to use the .capture
modifier. Otherwise the update
event is caught by the v-on:update:propertyName
on the child component and this does not bubble up to the parent.
So you can use v-on:update:name.capture="someMethod"
on the <oslo>
tag for example. And have this someMethod
in the parent's methods
. After this is called, the event will be triggered on the child component which will update the object and thereby the property.
All together:
let Oslo = {
props: {
name: String,
access: String
},
name: 'Oslo',
template: `<div>
<input type="text" :value="name" @change="$emit('update:name', $event.target.value)" />
<input type="text" :value="access" @change="$emit('update:access', $event.target.value)" />
</div>`
}
new Vue({
el: "#app",
components: {
Oslo,
},
data: {
thedata: {
name: 'Oslo name',
access: 'admin'
}
},
methods: {
nameWillBeUpdated: function(v) {
console.log('New value of name will be:', v);
// After this, the `update:name` event handler of the
// child component is triggered and the value will change.
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<span>{{this.thedata.name}} - {{this.thedata.access}}</span>
<oslo
v-bind.sync="thedata"
v-on:update:name.capture="nameWillBeUpdated"
/>
</div>