2👍
No, never. Vue js communication between parent/children is one way. Props received from parents should NEVER be mutated, otherwise, you will receive a warning if you are working with strict mode enabled.
Data from parents to children must be sent as props
. Data from children to parent must be sent within events
The best approach here is to manipulate the data locally in your child component, by setting a local data when the component is mounted, and then emitting an event to the parent when something changes (only if you need to let the parent know that something happened)
props: ['options'],
data: () => ({
local_options: []
}),
mounted () {
this.local_options = [ ...this.options]
},
watch: {
options: function () {
deep: true,
handler () {
this.local_options = [ ...this.options]
}
}
},
methods: {
updateOption(index, value) {
this.local_options[index].answer = value
this.$emit('updated', this.local_options)
},
removeOption(index) {
this.local_options.splice(index, 1)
this.$emit('updated', this.local_options)
},
addOption() {
this.local_options.push({
answer: '',
correct: false,
})
this.$emit('updated', this.local_options)
},
},
Then in your parent, where you have your child component, you can do something as per the child event:
<multi-options-component :options="options" @updated="doSomething" />
....
methods: {
doSomething (options) {
// Some logic here
}
}
2👍
I believe that the way @Luciano prefers having a separate local state for the child and manually maintaining its reactivity is a recipe for disaster (not to mention its way overkill considering Vue already has reactivity built-in). I have mentioned this in the comments, I’ll add here again:
- If
doSomething
fails for any reason, then the parent’soptions
array won’t get updated, whereaslocal_options
array has already been updated within the component. Now parent and local states are out of sync and you have problem. - There is no need to use deep
watcher
inside every reusable component you build. If you have hundreds of such child components, you’ll have performance hits and you’re unnecessarily consuming a lot of memory creating and maintaining a separate local state for each child component. - You’re losing the reactivity advantage that Vue provides in trying to manually ensure (albeit in an error-prone way) the local state is always in sync with the parent. That’s not how you’re supposed to use Vue.js.
So I’m adding another answer that’s better than my previous answer in terms of reusability and DRY principle, while also being simple and chaos-free:
You need to pass an array back to the parent along with a single custom update
event, but instead of creating this array in the mounted
lifecycle hook, you create it on the go in each method:
props: ['options'],
methods: {
updateOption(index, value) {
let local_options = [ ...this.options]
local_options[index].answer = value
this.$emit('update', local_options)
},
removeOption(index) {
let local_options = [ ...this.options]
local_options.splice(index, 1)
this.$emit('update', local_options)
},
addOption() {
let local_options = [ ...this.options]
local_options.push({
answer: '',
correct: false,
})
this.$emit('update', local_options)
},
},
Then update your parent component’s option
data when the child emits an update
event with v-on:
directive or its shorthand @
<multi-options :options="options" @update="handleUpdate" />
....
methods: {
handleUpdate(options) {
this.options = options; // this reassignment re-renders the child component
}
}
EDIT: I missed that you’re using v-model
on a prop in <b-form-checkbox>
element with v-model="option.correct"
. This will create a two way binding with the prop since it translates to :checked="option.correct" @change="option.correct = $event"
. Which means you’re mutating the prop inside child component. Do this instead:
<b-form-checkbox
:id="`option-${index}`"
name="options"
class="f-14 text-muted ml-1"
:checked="option.correct"
@change="setOptionsCorrect(index, $event)"
>
Correct?
</b-form-checkbox>
add the method under your methods
:
setOptionsCorrect(index, value) {
let local_options = [ ...this.options]
local_options[index].correct = value
this.$emit('update', local_options)
}
EDIT 2: If you insist on using v-model
for style preferences, you could have a computed property derive from the options. But you’ll need a separate setter that emits the event to parent, otherwise you’ll run into the same "mutating props" problem
computed : {
computedOptions: {
get: function() {
return this.options
},
set: function(newOptions) {
this.$emit("update", newOptions)
}
}
},
methods: {
updateOption(index, value) {
let local_options = [ ...this.options]
local_options[index].answer = value
this.options = local_options
},
removeOption(index) {
let local_options = [ ...this.options]
local_options.splice(index, 1)
this.options = local_options
},
addOption() {
let local_options = [ ...this.options]
local_options.push({
answer: '',
correct: false,
})
this.options = local_options
},
}
- [Vuejs]-Can't add dynamic validation using vue-validator
- [Vuejs]-Tailwind css Classes are not included if they are type in a string vue
0👍
You’re not supposed to mutate props from the component itself. From docs, this is to prevent child component from accidentally mutating parent components state, which can make your app’s data flow harder to understand.
What you can do instead is emit custom events from child component:
updateOption(index, value) {
this.$emit("myUpdateEvent", { index, value });
}
removeOption(index) {
this.$emit("myRemoveEvent", { index });
}
addOption() {
this.$emit("myAddEvent");
}
Then listen to them in the parent component and make changes to the original array that you’re passing to the child component as props.
<multi-options @myAddEvent="handleAddEvent()" @myUpdateEvent="handleUpdateEvent($event.index, $event.value)" @myRemoveEvent="handleRemoveEvent($event.index)" :options="myOptions"/>
data() {
return {
myOptions: [],
}
},
methods: {
handleUpdateEvent(index, value) {
this.myOptions[index].answer = value
},
handleRemoveEvent(index) {
this.myOptions.splice(index, 1);
},
handleAddEvent() {
this.myOptions.push({
answer: '',
correct: false,
});
}
}