I am trying to (deep) watch for any changes on a reactive object in Vue 3:
import { reactive, watchEffect } from 'vue';
const settings = reactive({
panes: {
left: {
defaultAction: "openInOtherPane",
},
right: {
defaultAction: "openInThisPane",
},
},
showMenu: false,
isDrawerOpen: false,
});
watchEffect(() => {
console.log('The new settings are', settings);
});
// Try to change a setting (this does not trigger watchEffect)
settings.panes.left.defaultAction = "none";
This code has two problems:
- The watchEffect is not triggered when settings are changed
- The console.log displays an unreadable object in the form of
Proxy { <target>: {…}, <handler>: {…} }
I also tried watch
:
watch(
() => settings,
settings => {
console.log('The new settings are', settings);
},
);
With the same problems.
When I only watch a property on the object, then it does work:
watchEffect(() => {
console.log('Is the menu shown:', settings.showMenu); // This works
});
And with destructuring I can watch changes one level deep, but not more levels:
watchEffect(() => {
// This works one level deep
console.log('Settings are changed:', { ...settings });
});
The settings can be logged using toRaw, but then the watchEffect is not triggered:
import { toRaw, watchEffect } from 'vue';
watchEffect(() => {
// console.log shows the raw object, but watchEffect is not triggered
console.log('Settings are changed:', toRaw(settings) );
});
Is there an elegant method to watch for changes on properties any level deep in a reactive object?
I am trying to (deep) watch for any changes on a reactive object in Vue 3:
import { reactive, watchEffect } from 'vue';
const settings = reactive({
panes: {
left: {
defaultAction: "openInOtherPane",
},
right: {
defaultAction: "openInThisPane",
},
},
showMenu: false,
isDrawerOpen: false,
});
watchEffect(() => {
console.log('The new settings are', settings);
});
// Try to change a setting (this does not trigger watchEffect)
settings.panes.left.defaultAction = "none";
This code has two problems:
- The watchEffect is not triggered when settings are changed
- The console.log displays an unreadable object in the form of
Proxy { <target>: {…}, <handler>: {…} }
I also tried watch
:
watch(
() => settings,
settings => {
console.log('The new settings are', settings);
},
);
With the same problems.
When I only watch a property on the object, then it does work:
watchEffect(() => {
console.log('Is the menu shown:', settings.showMenu); // This works
});
And with destructuring I can watch changes one level deep, but not more levels:
watchEffect(() => {
// This works one level deep
console.log('Settings are changed:', { ...settings });
});
The settings can be logged using toRaw, but then the watchEffect is not triggered:
import { toRaw, watchEffect } from 'vue';
watchEffect(() => {
// console.log shows the raw object, but watchEffect is not triggered
console.log('Settings are changed:', toRaw(settings) );
});
Is there an elegant method to watch for changes on properties any level deep in a reactive object?
Share Improve this question edited Feb 5, 2021 at 13:30 Hendrik Jan asked Feb 5, 2021 at 12:28 Hendrik JanHendrik Jan 4,9089 gold badges47 silver badges78 bronze badges3 Answers
Reset to default 16After some better reading of documentation, I found that you can give watch()
the option { deep: true }
like this:
import { toRaw, watch } from 'vue';
watch(
() => settings,
settings => {
// use toRaw here to get a readable console.log result
console.log('settings have changed', toRaw(settings));
},
{ deep: true },
)
watchEffect cannot be used in this way, because watchEffect is triggered by references to reactive properties, and you would have to loop through the whole object and do something with al the values to trigger it.
watch()
with { deep: true }
seems to be the best option.
If it is enough to watch for changes in the first level properties of an object, this worked surprisingly well for me:
watchEffect(() => {
toRefs(objectToWatch)
// perform some side effect
})
Watching Reactive Objects
Using a watcher to compare values of an array or object that are reactive requires that it has a copy made of just the values.
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers);
})
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]