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

Possible regression? Reactivity on store members triggers infinite loop in svelte 5 (works in svelte 4) #14306

Closed
fnimick opened this issue Nov 14, 2024 · 4 comments

Comments

@fnimick
Copy link

fnimick commented Nov 14, 2024

Describe the bug

When using a store for reactivity, where the store contains an object, svelte 4 allows a reactive block to set a store member based on the value of another store member, without triggering reactivity on the whole store.

In svelte 5, this appears to no longer be possible - assignments to $store.foo trigger reactivity on $store.bar.

Example: the svelte 4 version runs once, the svelte 5 version triggers an infinite loop, when the button is clicked.

<script>
	import { writable } from 'svelte/store';
	let s = writable({foo: 'foo', bar: null});

	$: {
		if ($s.foo === 'baz') {
			$s.bar = 'some';
		} else {
			$s.bar = null;
		}
	}

	// $effect(() => {
	// 	if ($s.foo === 'baz') {
	// 		$s.bar = 'some';
	// 	} else {
	// 		$s.bar = null;
	// 	}
	// })
</script>

<p>{JSON.stringify($s)}</p>
<button onclick={() => {$s.foo = ($s.foo === 'baz' ? 'foo' : 'baz')}}>modify</button>

I'm not sure how to work around this when dealing with libraries that use stores - I am not in control here, and need to update the value of one store member when another store member changes.

Reproduction

https://svelte.dev/playground/hello-world?version=5.2.0#H4sIAAAAAAAAE32QT0-EMBDFv8qkIQESQu8IGG_Ggx48Wg_8mZrG0pJ21nUl_e6mC6jRjadp5v2m780szHQTsordotYWjtbpETIcFeGYs4JJpdGz6mlhdJojFxus2Kdu5rn0b6gp9vrO46X-YA2hIc8qVvvBqZlaYQSpabaOYIGjU9T1GiGAdHaCdJ3knqzD9CqyGgk8NF9otkhrK0iltWkBfecqMAetQx5pIyipYIlVkJKQJb6U1kLTNJD23Uea76KgxJd956CB1NtpMxMUALXHC1Q02ZlYwmrHOSQoJQ6UZTk07TrJOfzrf9YvRzhLP1P8Zr-CrOT2CrkwNf--sanndrl7fLgvPTllXpQ8ZYnPQ83nVpi6PxBZA9YMWg2vzbJl3-P-DQ7X68mh2hYJoZ3sqOSp5utnLSsY4TuxitwBw3P4BNax-WxhAgAA

Logs

No response

System Info

System:
    OS: macOS 15.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 128.16 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.13.1 - ~/.asdf/installs/nodejs/20.13.1/bin/node
    Yarn: 1.22.19 - ~/.yarn/bin/yarn
    npm: 10.5.2 - ~/.asdf/plugins/nodejs/shims/npm
    pnpm: 9.7.0 - ~/.asdf/shims/pnpm
  Browsers:
    Chrome: 130.0.6723.117
    Safari: 18.1
  npmPackages:
    svelte: ^5.0.0 => 5.2.0

Severity

blocking an upgrade

@Prinzhorn
Copy link
Contributor

$effect(() => {
	if ($s.foo === 'baz') {
		untrack(() => $s).bar = 'some';
	} else {
		untrack(() => $s).bar = null;
	}
})

https://svelte.dev/playground/hello-world?version=5.2.0#H4sIAAAAAAAAA4WQS0vFMBCF_8oQCm2htPvaVNyJC124tC76mEi4aaYkU6_Xkv8uvbdVfICLIXDyneGcWYRtRxSluEVjCI7kzAAJDppxSEUmlDboRfm0CD5NK7cKIttdN9OU-1c0vGpd6_EvvSfLaNmLUlS-d3riurEN63Eix7DAbNm1_QECKEcjxBdjfPUNOjrNbWfwB1V4JrexBhk8yE80WRRRCbEiijPoWleCnY0J6ZleJ0KlsOckSUHWsJxl1gqSyOeKCKSUEHfte5zunw1vaTdP5NO8ax1IiD2NW5CGA6Dx-L9pzbNb1iekja2KryPZaqqXu8eH-9yz0_ZFq1MS-TRUxVQ3tupmZrJAtje6P8hl67Gn_90Dri_ngHLrFUI90qDVqSouy2qRCcY3FiW7GcNz-ACalO0tIgIAAA==

I got that idea from the code that Svelte generates in the original example:

$.legacy_pre_effect(() => ($s()), () => {
	if ($s().foo === 'baz') {
		$.store_mutate(s, $.untrack($s).bar = 'some', $.untrack($s));
	} else {
		$.store_mutate(s, $.untrack($s).bar = null, $.untrack($s));
	}
});

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Nov 15, 2024

Oh wait, that doesn't actually have the same outcome 😄 . Maybe it gets you on the right track? What definitely works if wrapping the assignments in if-block to check if it's already null/'some'

@dummdidumm
Copy link
Member

This works as expected. $: statements only run once, $effect runs whenever one of the dependencies it reads changes. The store changes on read because you keep reassigning to it, so the $effect reruns in a loop. One solution is to guard the write to not do it when not necessary. playground link with if solution

The better solution is probably to rework the code so that you do the additional update at the point where the store is initially updated (inside the event in case of the REPL) - not sure how applicable that is in your case.

Either way, this works as expected, therefore closing.

@dummdidumm dummdidumm closed this as not planned Won't fix, can't repro, duplicate, stale Nov 15, 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

3 participants