6

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?

Hendrik Jan
  • 4,396
  • 8
  • 39
  • 75

3 Answers3

12

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

Hendrik Jan
  • 4,396
  • 8
  • 39
  • 75
  • that worked for me too, but what I do not understand is in the docs https://vuejs.org/guide/essentials/watchers.html#deep-watchers deep watching is implicit for reactive object so we should not need to add deep: true ! – walox Jan 04 '23 at 09:12
3

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
})
martindzejky
  • 389
  • 1
  • 3
  • 15
0

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]
Haron
  • 2,371
  • 20
  • 27