watch-effect behaves differently with dev-server vs. production build.
Version
3.2.31
Reproduction link
Steps to reproduce
Run the minimal reproduction (main branch) twice: Once with the dev-server ( npm run serve
) and once with a production build (npm run build
).
(A) Setup:
- Navigate to the project in a browser.
- You will see three things:
2.1 The output of the variable
isWorking
being set tofalse
. 2.2 A button titled "SetisWorking
to true and change it back after 5 seconds". 2.3 A button titled "Trigger WatchEffect". - Open the browser console.
(B) Click the buttons and observe the browser console for the first time:
- Click the left button. This will change
isWorking
totrue
for 5 seconds, and change it back afterwards. - While
isWorking
is stilltrue
, click the right button now. - This will trigger the following console.log: "[is-working-watch-effect@startWatchEffect@watchEffect][1] Is currently working"
- When
isWorking
is changing back tofalse
, the following console.log will be triggered: "[is-working-watch-effect@startWatchEffect][2] Is not working anymore"
(C) Click the buttons and observe the browser console for the second time:
- Click the left button again.
- While
isWorking
is stilltrue
, click the right button now again. - This will trigger the following console.log again: "[is-working-watch-effect@startWatchEffect@watchEffect][1] Is currently working"
- Point of divergence:
4.1 [DEV-SERVER] When
isWorking
is changing back tofalse
, the following console.log will be triggered: "[is-working-watch-effect@startWatchEffect][2] Is not working anymore". This only happens on the DEV-SERVER-build. 4.2 [PRODUCTION BUILD] WhenisWorking
is changing back tofalse
, no further console.log will be printed. This will actually not work again until the page is refreshed.
What is expected?
It is expected that a Dev-Server-build and a Production-build work the same way.
It is expected that
watchEffect
works the same every time (both lines are printed, instead of just the first).
What is actually happening?
The Dev-Server build always prints both lines for
watchEffect
. The Production-build only prints both lines forwatchEffect
once. After that it will always only ever print the first line.In the Production-build
watchEffect
is only printing both lines once (the first time).
I spent a lot of time debugging this. Here are a few additional notes:
Using
watch
instead ofwatchEffect
works reliably! In fact, I added a branch to make it easy to reproduce that: https://github.com/SvenSchoene/vue3-bug-reactivity-watch-effect-01/tree/its-working-with-watch-instead-of-watchEffectCurrent Vue-versions output a "modern" and a "legacy" version (
common.js
andcommon-legacy.js
) It doesn't matter if one uses one or the other. The weird (erroneous?) behavior does not seem to be explained by that.This code actually always worked in our Vue 3 project! We've been using
vue@3.1.0-beta.4
for a long time now. Only after the upgrade to the newest version did this break for us. In fact, I added another branch to make it easy to reproduce this: https://github.com/SvenSchoene/vue3-bug-reactivity-watch-effect-01/tree/prove-that-its-working-with-vue-3.1.0-beta.4We can reproduce this issue with both, Node 15 and Node 16.
If you open the file
src/is-working-watch-effect.js
(https://github.com/SvenSchoene/vue3-bug-reactivity-watch-effect-01/blob/main/src/is-working-watch-effect.js#L14-L16) you can see in lines 14 to 16 that theStopHandle
(https://vuejs.org/api/reactivity-core.html#watcheffect) is being called from within thewatchEffect
. If this is NOT being called from there, the issue will not occur. I hope that this might be a good lead for starting to track this issue down and see where the reactivity tracking is being dropped or going out of scope or whatever.
(I admit that it might be considered weird to stop the watchEffect
from within itself. For a particular feature (=waiting until saving is done in our application) this does seem to be a sensible implementation to me.
Even if you might disagree that this is a sensible implementation, I'm sure you agree that this should behave the same in all cases.)
- I recorded two videos of this behavior:
(A) This is a video from the Dev-Server. This is working as intended:
https://user-images.githubusercontent.com/7631673/163072529-e5e3f1a0-1997-4385-85aa-08193cb6dd04.mp4
(B) This is a video from the Production-Build. This is broken / inconsistent / not working as expected / intended:
https://user-images.githubusercontent.com/7631673/163072746-36fa2023-a4b6-4fe2-8972-38055c8dd4a4.mp4
Our biggest issue is that this discrepancy is undermining the trust that we have in Vue's reactivity. We have an application with ~50 Vuex stores, hundreds of components and lots and lots of reactivity.
There have been other weird reactivity issues since upgrading to the newest version. We haven't been able to reproduce those as concisely as this one yet.
It is obviously really really important for us that our developers can come across reactivity issues in their Dev-Server builds, before it's caught later on a Staging or Live build.
I appreciate the quick fix. It looks good, can't wait to test it out!
Is there any insight on why there would be a difference between a Dev-Server and a Production-build on this matter?
Again, we wouldn't have been able to catch this issue during development-time, and that is a bit concerning.
I wonder if this is related to this, or something like this: https://github.com/vuejs/core/issues/5655#issuecomment-1086613873
I have still the issue in v3.2.45. Confirmed in v3.2.31, upgraded to v3.2.45 and the bug is still present.