how to trigger a watcher even when assigned to the same newVal

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 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..

    – 

  • 1

  • 1

    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.

    – 

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 to emits:
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 as this.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.

Leave a Comment