Subscribe on changes!

Combination of watch, computed and h creates an infinite loop after building

avatar
Apr 20th 2023

Vue version

3.3.0-alpha.13

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-ccxqc4?file=src/components/BuggyForm.vue

Steps to reproduce

  1. Go to the StackBlitz URL. You should see a yellow interface at the right (If the server is not running, type npm run dev in the terminal).
  2. Change the country from United States to Spain.
  3. Observe how the state automatically is changed to the first option.
  4. Stop the server in the terminal (Cmd/Ctrl+C) and run npm run preview to see how it would work after doing build.
  5. Open devtools (we added a few console.debug to throttle the infinite loop and avoid hanging out the browser), and change the country from United States to Spain again.

What is expected?

The same behavior as in step 3.

What is actually happening?

An infinite loop.

System Info

All browsers and OSes.

Any additional comments?

The original bug only happened to us when building the application for production.

The BuggyForm component is a minimal version of our custom form component, which can have more components of the same type inside that are the form controls. Thus, the v-model of the input wthi name country would be "United States", while the v-model of its parent node, the form, would be {"country": "United States", "state": "California"}. To do that we need to double-bind the form slot children to the form, which is why we need to use the h function.

In the BuggyForm component, we watch for changes on the options or value when the input is a type="select", to make sure it always contain a valid value. Thus, if the current value is not in the options, it is reset to the first option available. However, this is triggering an update that calls this watcher again, with the old value ONLY when using a build (or vite preview).

It can also be triggered in Vue SFC Playground, but it's not possible to run it in dev mode to see the normal behavior.

avatar
Apr 21st 2023

Is this a regression only in 3.3 alpha, or reproducible in 3.2?

avatar
Apr 21st 2023

The playground example also breaks with 3.2.47 selected

avatar
May 3rd 2023

I forked the SFC Playground project to add some extra logging about the bug. You can test it out by opening the devtools console, clearing it and changing the "United States" selector to "Spain". You will see the following:

Expected behavior (the one that happens in dev mode)

  1. [select] emit new value: Spain
  2. [form] emit new value: {country: 'Spain', state: 'California'}
  3. [App] applying for v-model changes: {country: 'Spain', state: 'California'}
  4. [App] changing states based on new country: Spain
  5. [select] new value (California) is not in options (Madrid,Barcelona), emitting Madrid
  6. [form] emit new value: {country: 'Spain', state: 'Madrid'}
  7. [App] applying for v-model changes: {country: 'Spain', state: 'Madrid'}
  8. [App] changing states based on new country: Spain

Explanation: when you set the new country, the select change is emitted, and the form emits it up recursively. Then, the component user (App in this case) applies the v-model changes, and since the second selector depends on the first one, the computed is re-run to update the second selector options. Then, the form component detects a change on its value/options, and proceeds to check that the new value is one of the existing options. Since it's not, it will emit a change again, this time to the first option available, and the event will reach the form and then the app, which will apply the changes.

Actual behavior (the one that happens after build):

  1. [select] emit new value: Spain
  2. [form] emit new value: {country: 'Spain', state: 'California'}
  3. [App] applying for v-model changes: {country: 'Spain', state: 'California'}
  4. [App] changing states based on new country: Spain
  5. [select] new value (California) is not in options (Madrid,Barcelona), emitting Madrid
  6. [form] emit new value: {country: 'Spain', state: 'Madrid'}
  7. [App] applying for v-model changes: {country: 'Spain', state: 'Madrid'}
  8. [App] changing states based on new country: Spain
  9. [select] new value (California) is not in options (Madrid,Barcelona), emitting Madrid
  10. [form] emit new value: {country: 'Spain', state: 'Madrid'}
  11. [App] applying for v-model changes: {country: 'Spain', state: 'Madrid'}
  12. [App] changing states based on new country: Spain
  13. ...

Explanation: as you can see, lines 4-7 are the same as 8-11, forever since this is an infinite loop. The actual difference between dev and prod occurs in the line 9, when the watcher in BuggyForm receives the oldValue (California) instead of the new one (Madrid).

TL;DR the core of this issue is: a watcher is triggered, but when on build mode the newValues are actually the old ones

avatar
Jun 21st 2023

Is there any update / some way I can help to fix it?