While Vue Composition API RFC Reference site has many advanced use scenarios with the watch
module, there are no examples of how to watch component props.
Neither is it mentioned in Vue Composition API RFC's main page or vuejs/composition-api in Github.
I've created a Codesandbox to elaborate on this issue.
<template>
<div id="app">
<img width="25%" src="./assets/logo.png">
<br>
<p>Prop watch demo with select input using v-model:</p>
<PropWatchDemo :selected="testValue"/>
</div>
</template>
<script>
import { createComponent, onMounted, ref } from "@vue/composition-api";
import PropWatchDemo from "./components/PropWatchDemo.vue";
export default createComponent({
name: "App",
components: {
PropWatchDemo
},
setup: (props, context) => {
const testValue = ref("initial");
onMounted(() => {
setTimeout(() => {
console.log("Changing input prop value after 3s delay");
testValue.value = "changed";
// This value change does not trigger watchers?
}, 3000);
});
return {
testValue
};
}
});
</script>
<template>
<select v-model="selected">
<option value="null">null value</option>
<option value>Empty value</option>
</select>
</template>
<script>
import { createComponent, watch } from "@vue/composition-api";
export default createComponent({
name: "MyInput",
props: {
selected: {
type: [String, Number],
required: true
}
},
setup(props) {
console.log("Setup props:", props);
watch((first, second) => {
console.log("Watch function called with args:", first, second);
// First arg function registerCleanup, second is undefined
});
// watch(props, (first, second) => {
// console.log("Watch props function called with args:", first, second);
// // Logs error:
// // Failed watching path: "[object Object]" Watcher only accepts simple
// // dot-delimited paths. For full control, use a function instead.
// })
watch(props.selected, (first, second) => {
console.log(
"Watch props.selected function called with args:",
first,
second
);
// Both props are undefined so it's just a bare callback func to be run
});
return {};
}
});
</script>
Although my question and code example were initially with JavaScript, I'm using TypeScript. Tony Tom's first answer although working, led to a type error. Which was solved by Michal Levý's answer. So I've tagged this question with typescript
afterward.
Here is my polished yet barebones version of the reactive wirings for this custom select component, on top of <b-form-select>
from bootstrap-vue
(otherwise agnostic-implementation but this underlying component does emit @input and @change events both, based on whether the change was made programmatically or by user interaction).
<template>
<b-form-select
v-model="selected"
:options="{}"
@input="handleSelection('input', $event)"
@change="handleSelection('change', $event)"
/>
</template>
<script lang="ts">
import {
createComponent, SetupContext, Ref, ref, watch, computed,
} from '@vue/composition-api';
interface Props {
value?: string | number | boolean;
}
export default createComponent({
name: 'CustomSelect',
props: {
value: {
type: [String, Number, Boolean],
required: false, // Accepts null and undefined as well
},
},
setup(props: Props, context: SetupContext) {
// Create a Ref from prop, as two-way binding is allowed only with sync -modifier,
// with passing prop in parent and explicitly emitting update event on child:
// Ref: https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
// Ref: https://medium.com/@jithilmt/vue-js-2-two-way-data-binding-in-parent-and-child-components-1cd271c501ba
const selected: Ref<Props['value']> = ref(props.value);
const handleSelection = function emitUpdate(type: 'input' | 'change', value: Props['value']) {
// For sync -modifier where 'value' is the prop name
context.emit('update:value', value);
// For @input and/or @change event propagation
// @input emitted by the select component when value changed <programmatically>
// @change AND @input both emitted on <user interaction>
context.emit(type, value);
};
// Watch prop value change and assign to value 'selected' Ref
watch(() => props.value, (newValue: Props['value']) => {
selected.value = newValue;
});
return {
selected,
handleSelection,
};
},
});
</script>
watch
on the props which you take into thesetup
function? First make them into `Refs, basiacally make a reactive copy and it should fire on subsequent changes. – Sommersommers