Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jcarlosroldan opened this issue Apr 20, 2023 · 5 comments

Comments

@jcarlosroldan
Copy link

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.

@yyx990803
Copy link
Member

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

@LinusBorg
Copy link
Member

The playground example also breaks with 3.2.47 selected

@jcarlosroldan
Copy link
Author

jcarlosroldan commented May 3, 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

@jcarlosroldan
Copy link
Author

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

@edison1105
Copy link
Member

I don't think this is an internal vue problem.
You should to pass the value as below:

<BuggyForm type="select" name="state" :value="formValue.state" :options="stateOptions"/>

see playground

@edison1105 edison1105 closed this as not planned Won't fix, can't repro, duplicate, stale Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants