How to Watch Props Change with Vue Composition API / Vue 3?
This does not address the question of how to "watch" properties. But if you want to know how to make props responsive with Vue's Composition API, then read on. In most cases you shouldn't have to write a bunch of code to "watch" things (unless you're creating side effects after changes).
The secret is this: Component props
IS reactive. As soon as you access a particular prop, it is NOT reactive. This process of dividing out or accessing a part of an object is referred to as "destructuring". In the new Composition API you need to get used to thinking about this all the time--it's a key part of the decision to use reactive()
vs ref()
.
So what I'm suggesting (code below) is that you take the property you need and make it a ref
if you want to preserve reactivity:
export default defineComponent({
name: 'MyAwesomestComponent',
props: {
title: {
type: String,
required: true,
},
todos: {
type: Array as PropType<Todo[]>,
default: () => [],
},
...
},
setup(props){ // this is important--pass the root props object in!!!
...
// Now I need a reactive reference to my "todos" array...
var todoRef = toRefs(props).todos
...
// I can pass todoRef anywhere, with reactivity intact--changes from parents will flow automatically.
// To access the "raw" value again:
todoRef.value
// Soon we'll have "unref" or "toRaw" or some official way to unwrap a ref object
// But for now you can just access the magical ".value" attribute
}
}
I sure hope the Vue wizards can figure out how to make this easier... but as far as I know this is the type of code we'll have to write with the Composition API.
Here is a link to the official documentation, where they caution you directly against destructuring props.
If you take a look at watch
typing here it's clear the first argument of watch
can be array, function or Ref<T>
props
passed to setup
function is reactive object (made probably by readonly(reactive())
, it's properties are getters. So what you doing is passing the value of the getter as the 1st argument of watch
- string "initial" in this case. Because Vue 2 $watch
API is used under the hood (and same function exists in Vue 3), you are effectively trying to watch non-existent property with name "initial" on your component instance.
Your callback is called only once and never again. Reason it is called at least once is because new watch
API is behaving like current $watch
with immediate
option (UPDATE 03/03/2021 - this was later changed and in release version of Vue 3, watch
is lazy same way as it was in Vue 2)
So by accident you doing the same thing Tony Tom suggested but with wrong value. In both cases it's not valid code if you are using TypeScript
You can do this instead:
watch(() => props.selected, (first, second) => {
console.log(
"Watch props.selected function called with args:",
first,
second
);
});
Here the 1st function is executed immediately by Vue to collect dependencies (to know what should trigger the callback) and 2nd function is the callback itself.
Other way would be to convert props object using toRefs
so it's properties would be of type Ref<T>
and you can pass them as a 1st argument of watch
Anyway, most of the time watching props is just not needed - simply use props.xxx
directly in your template (or setup
) and let the Vue do the rest
I just wanted to add some more details to the answer above. As Michal mentioned, the props
coming is an object and is reactive as a whole. But, each key in the props object is not reactive on its own.
We need to adjust the watch
signature for a value in the reactive
object compared to a ref
value
// watching value of a reactive object (watching a getter)
watch(() => props.selected, (selection, prevSelection) => {
/* ... */
})
// directly watching a ref
const selected = ref(props.selected)
watch(selected, (selection, prevSelection) => {
/* ... */
})
Just some more info even though it's not the mentioned case in the question: If we want to watch on multiple properties, one can pass an array instead of a single reference
// Watching Multiple Sources
watch([ref1, ref2, ...], ([refVal1, refVal2, ...],[prevRef1, prevRef2, ...]) => {
/* ... */
})