Subscribe on changes!

watch onInvalidate callback is triggered many times

avatar
Dec 22nd 2021

Version

3.2.26

Reproduction link

sfc.vuejs.org/

Steps to reproduce

  1. Click on button multiple times
  2. Watch the console output

What is expected?

Callback that we pass to onInvalidate calls only once. In reproduction repo I expect that abort would be called the same times as fetch

const count = ref(0);

watch(count, (value, oldValue, onInvalidate) => {
  if (value > 2) {
    return;
  }
  
  const controller = new AbortController();
  
  // fetch would be called only 2 times
  console.log('fetch!');
  fetch('/test', { signal: controller.signal });

  onInvalidate(() => {
    // but onInvalidate would be calld every time watch triggers
    console.log('aborted!');
    controller.abort();
  });
});

What is actually happening?

Callback that we pass to onInvalidate is called multiple times

In the screenshot fetch was called 2 times, but abort was called 18 times image


There is enchancement issue that would fix this behaviour, I think. https://github.com/vuejs/vue-next/issues/3341

avatar
Dec 22nd 2021

This is a tricky one, feels like a definition gap. If we change the behavior now, we risk breaking existing code. We might still want to if we decide the proposed behavior was the originally intended one anyway.

For now, one could work around this like so:

const count = ref(0);

watch(count, (value, oldValue, onInvalidate) => {
  if (value > 2) {
    return;
  }
  
  const controller = new AbortController();
  
  // fetch would be called only 2 times
  console.log('fetch!');
  fetch('/test', { signal: controller.signal });

  let invalidated = false
  onInvalidate(() => {
    if (invalidated) return
    invalidated = true
    console.log('aborted!');
    controller.abort();
  });
});
avatar
Dec 23rd 2021

Thanks for work around and answer! For me "onInvalidate" means callback that fired when "valid" state changes to "invalid". Right now this callback may be called even when "state" has already "invalid". This cause some weirdness. But yeah, the existing code matters here...

avatar
Apr 20th 2023

Another work around

BTW, this behavior is werid. I'm using vue 3, and thought onCleanup(disposer) is equal to React's return disposer, and even should accept multiple disposers, see https://github.com/vuejs/core/issues/3341

const count = ref(0);

watch(count, (value, oldValue, onInvalidate) => {
  // ------ Add this line ------
  onInvalidate(()=>{})
  // ---------------------------
  if (value > 2) {
    return;
  }
  
  const controller = new AbortController();
  
  // fetch would be called only 2 times
  console.log('fetch!');
  fetch('/test', { signal: controller.signal });

  onInvalidate(() => {
    // but onInvalidate would be calld every time watch triggers
    console.log('aborted!');
    controller.abort();
  });
});
avatar
Jun 27th 2023