Subscribe on changes!

`triggerRef` does not trigger `watch(() => ref.value)`

avatar
Nov 10th 2023

Vue version

3.3.8

Minimal reproduction

This test fails:

import { nextTick } from 'vue'
test('triggerRef with watch', async () => {
    const a = shallowRef(1)
    let aTriggered = false
    watch(() => a.value, () => {
      aTriggered = true
    })
    triggerRef(a)

    await nextTick()  

    expect(aTriggered).toBe(true)
  })

What is expected?

Watching values of manually triggered references should execute the watch callback.

What is actually happening?

Watching the ref itself works fine. According to post by @LinusBorg in https://github.com/vuejs/core/issues/1209#issuecomment-648953517, this is the expected design of Vue.

However, getting value of the ref does not work. So watch(() => a.value, ...) won't work but, watch(a, ...) work. The behavior should be the same and consistent for shallowRefs where state tracking is disabled

watchEffect works fine for these .value calls. This is another undocumented inconsistency with watch.

Official Vue docs state nothing about caching for watch.

avatar
Nov 10th 2023

My linked comment is incorrect as it relates to triggerRef. I was, at that moment, more focused on the asynchronous execution of the test. Further down in the linked issue'S comments, Evan explains why the watch callback should not execute in these cases - because the callback should only run if the value actually changed, which is also documented as such, in my opinion.

(Sidenote: watchEffect is different as it doesn't have a callback, it essentially is a watch with just the first argument - only the effect, not the callback. And the effect runs on any trigger, yes.)

The real inconsistency that I noticed is that the behaviour you desire is only happening if the first argument is a shallowRef:

const a = shallowRef(0)
watch(a, () => console.log('this will trigger'))
watch(() => a.value () => console.log(''this will not'))

playground

Looking at the implementation of watch() this could be by design? I have to call in @yyx990803 to get some insight in the desired behaviour.

I can presume this was done so that one can use watch() when doing mutations to in non-reactive content in a shallowRef's value, but I personally think this behaviour is confusing, especially since it only works with the ref as the argument, not a getter reading the ref.

Though I could imagine that there's already userland code out there relying on this behavior, which we don't want to break, necessarily.

if we keep this behavior, we need to properly document it as a special behavior of watch() with shallowRef()