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

Functions in reactive statements do not invalidate variables #7971

Closed
kevinleto-informaticon opened this issue Oct 24, 2022 · 6 comments
Closed

Comments

@kevinleto-informaticon
Copy link

Describe the bug

If a function is called in a reactive statement $: and changes the value of other variables, these variables won't be invalidated and the reactivity chain won't be called for them.

If the change is made in place (without calling other functions), it works fine.

Not sure why a function call makes that big difference, I wouldn't expect it 😕
Thanks in advance for the help 😄

Reproduction

The issue is reproduced in the following REPL:
https://svelte.dev/repl/8945e581e9ac4638ad9870eb59cb97c1?version=3.52.0

The component is a counter and adds one to the count variable every time the button is pressed.
As soon as the limit (4) is reached, the count starts again from zero.
A boolean flag reset shows if the count has already reached its limit or not.

If the update of the variables count and reset is made in the change() functions, the reactive statements at lines 5 and 6 are not executed (the console doesn't show count: 0 and reset: true).

If the change is made directly in the reactive statement without calling the change() function, all works fine and the console shows the changes.

$: if(count > 3) {
  count = 0;
  reset = true;
}

Another workaround is to call the await tick() before calling change().

Logs

No response

System Info

System:
    OS: Windows 10 10.0.19044
    CPU: (8) x64 Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz    
    Memory: 3.25 GB / 15.75 GB
  Binaries:
    Node: 14.18.2 - C:\Program Files\nodejs\node.EXE
    npm: 6.14.15 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 106.0.5249.119
    Edge: Spartan (44.19041.1266.0), Chromium (106.0.1370.52)
    Internet Explorer: 11.0.19041.1566
  npmPackages:
    svelte: ~3.52.0 => 3.52.0
    webpack: ~4.43.0 => 4.43.0

Severity

annoyance

@brunnerh
Copy link
Member

This is not about invalidation but execution order of the reactive statements. The invalidation happens but after the log statements.

The problem is that the dependency chain is hidden. Hence the automatic topological ordering does not take the effects of the function into consideration. Because of this, the manual order of the reactive statements starts to matter.

You can fix the chain by manually ordering the statements correctly:

$: if(count > 3) {
  change();
}
$: console.log("count:", count);
$: console.log("reset:", reset);

@Conduitry
Copy link
Member

Yes, this is the intended behavior, and has come up several times before. See my comment here.

@Conduitry Conduitry closed this as not planned Won't fix, can't repro, duplicate, stale Oct 24, 2022
@kevinleto-informaticon
Copy link
Author

Thank you both for your answers!

Yes, I know that the topological order could matter sometimes.
But I would expect that as soon as a variable changes (count, but also reset) all related reactive statements are executed (again if needed), but at least with the definitive value (the last one).

It's not clear to me, why this doesn't happen...
In fact, the $: console.log("reset:", reset); won't be executed at all.

I would expect something like:

  1. count is set to 4
  2. optionally (but not necessary) $: console.log("count:", count); is executed (prints 4)
  3. $: if(count > 3) change(); is executed. count and reset are set respectively to 0 and true.
  4. $: console.log("count:", count); is executed (prints 0)
  5. $: console.log("reset:", reset); is executed (prints true)

Step 4 and 5 are skipped but I would expect that just the intermediate states could be skipped (count = 4).

@brunnerh
Copy link
Member

brunnerh commented Oct 24, 2022

It is pretty simple, you can look at the compiled code:

	$$self.$$.update = () => {
		if ($$self.$$.dirty & /*count*/ 1) {
			$: console.log("count:", count);
		}

		if ($$self.$$.dirty & /*reset*/ 4) {
			$: console.log("reset:", reset);
		}

		if ($$self.$$.dirty & /*count*/ 1) {
			$: if (count > 3) {
				// doesn't invalidate 'count' and 'reset'
				change();
			}
		}
	};

All reactive statements are evaluated in sequence and at the end of the the update loop, the dirty flags will be reset.

  1. Execution passes by the log statements while nothing is dirty
  2. change() is called and dirty is set for count/reset
  3. The end of the update loop is reached and dirty is reset
    => Nothing happens until count/reset is changed again

@kevinleto-informaticon
Copy link
Author

Thanks a lot! Now is pretty clear to me, where is the error.
Svelte is not able to get the dependencies if the code is wrapped in a function and won't reorder the statements accordingly.
For this reason, I have to do it manually (that is a bit difficult for complicated components) or I have to wait that the update-loop is completely executed (await tick()) and then make the changes I want.

I would add this explanation to the documentation, if possible.
As the doc and the tutorial said, the usage of await tick() is necessary to wait for DOM updates.
That is not wrong but an incomplete explanation.

Again, thanks a lot for the help! 🙏

@brunnerh
Copy link
Member

tick() is not really meant for this case in particular but rather for something where you really have some DOM dependency, e.g. when you show a modal via a boolean flag & {#if} and you have to wait for the elements to be created so you can focus() the first input within the dialog.

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

3 participants