i am using vue3
with options api
as shown in the following stackbiltz
link here
i have a child-component that contains a button
that when clicked, the method refresh
should be invoked. the refresh()
sets true
to the event-emitter
as shown below in the code.
in the parent-component, i bind on the event-emitter
via v-model
as shown below in the code so that, as i expect, for every click on the button refresh
the body of the watched variable vModel
in the parent-component should be executed.
but what happens is, only for the very first time the button refresh
is clicked, the watched variable vModel
in the parent is executed.
to solve this issue, i used $forceUpdate
but had no effect.
I know that the cause of this behaviour is that in the refresh()
in the Child-component always sets true
to this.compPropsVmodel
without changing the value assigned to it, but i do not know how to solve this issue
parent-component:
<template>
<Child
v-model:vModel="vModel">
</Child>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
data() {
return {
vModel: false,
};
},
components: {
Child
},
watch: {
vModel(newVal, oldVal) {
console.log('@watch vModel');
},
},
}
</script>
Child-component:
<template>
<button
id="idBtnRefreshCalendar"
type="button"
@click="refresh">
refresh
</button>
</template>
<script>
let debugTag = 'D.Child.vue::';
let infoTag = 'I.Child.vue::';
let verboseTag = 'V.Child.vue::';
let warnTag = 'W.Child.vue::';
let errorTag = 'E.Child.vue::';
let WTFTag = 'WTF.Child.vue::';
//
export default {
name: 'Child',
data() {
return {
};
},
components: {},
props: {
vModel: {
type: null,
},
},
emits: {
'update:vModel': null,
},
computed: {
compPropsVModel: {
get() {
console.log('get.compPropsVModel:', this.vModel);
return this.vModel;
},
set(value) {
console.log('set.compPropsVModel:', value);
this.$emit('update:vModel', value);
},
},
},
methods: {
refresh() {
const msg = JSON.stringify({ msg: verboseTag + 'refresh():'});
console.log(msg);
this.compPropsVModel = true;
this.$forceUpdate();
},
},
};
</script>
You seem to expect watch
to get triggered without changing the watched value. Well, don’t, because that’s not what watch
was designed for. It only triggers when the value changes.
Calling $forceUpdate
forces a child re-render, implicitly checking if the watched value has changed. Since it hasn’t, the watch
callback won’t be executed.
If you’ll allow a more colourful comparison, expecting watch
to be triggered without changing the value is like placing laundry in a blender and expecting it to function as a washing machine. Don’t, because it won’t.
To trigger the execution of a parent function (method) from a child, here’s what would work:
- a) [recommended 👍] – pass the method down as event (
@someName="parentMethod"
). In child component, add it toemits
:
export default {
emits: ['someName']
}
…and call it using this.$emit('someName', ...args)
, which will trigger
parentMethod(...args)
in parent every single time. No need for v-model
or watch
.
- b) [not recommended 👎] – pass the method down as
prop
(:someName="parentMethod"
) and call it asthis.someName(...args)
. It works, but you might run into scoping issues (this
inside the function might not be the current component instance in all cases, which is why it’s not recommended).
As an alternative, if, for any reason, you still want to use v-model
+ watch
, you might want to set vModel
as boolean
and keep changing the value:
this.$emit('update:vModel', !this.vModel)
Since the value changes, the watch
callback will be executed.
You set a value. The watcher see that the value changes and triggers. Then you keep setting the same value. Why would the watcher fire if the same data is set? You can replace the value you emit to a counter that goes up every time and you can see it always fire the watcher.
Taken from the vue docs: “With the Options API, we can use the watch option to trigger a function whenever a reactive property changes“
Last but not least. You don’t need a watcher if you want to execute something every time you set something. Just put that logic in a function that you trigger vs a watcher..
stackblitz.com/edit/vue-gjxisx?file=src%2FApp.vue
Hi, it’s unclear what’s forceUpdate is supposed to do. You can check that it rerenders Child. You don’t need models and watchers for this, as said.
Show 1 more comment