Hendrik Jan
Hendrik Jan

Reputation: 4908

Vue 3 How to use watch or watchEffect (and console.log) on 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:

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?

Upvotes: 7

Views: 12080

Answers (3)

Hendrik Jan
Hendrik Jan

Reputation: 4908

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.

Upvotes: 16

martindzejky
martindzejky

Reputation: 396

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
})

Upvotes: 3

Haron
Haron

Reputation: 2619

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]

Upvotes: 0

Related Questions