Describe the bug
Store value is not up-to-date if using a $ subscription inside a subscription function.
To Reproduce
For a real life example look at this REPL.

Expected behavior
Value should be in sync.
Severity
Blocking
Do you have a reproduction with fewer moving parts? Are the spreads an important part of this? Is the text input?
Do you have a reproduction with fewer moving parts? Are the spreads an important part of this? Is the text input?
The text input is not important, you can just do the following:
Then, the result would be the same.
Update:
I updated my original post with a shorter REPL that's probably easier to understand.
I'm going to disagree that it's a blocker because replacing line 12 with: $count = 0 fixes the issue.
However I'm going to agree that it looks like a bug, for the reason that:
count.set(0)
$count = 0
should have the same behaviour, I believe.
I'm going to disagree that it's a blocker because replacing line 12 with:
$count = 0fixes the issue.However I'm going to agree that it looks like a bug, for the reason that:
count.set(0) $count = 0should have the same behaviour, I believe.
And it's also not working correctly for count.update(v => 0) as well, which means that for updates you have to use this syntax instead:
$store = { ...$store, page: 0 }
AFAIK in the docs it says store.set(value) is the proper way to set a store's value, so I would say it's a bug that it doesn't work as described.
And if $count = 0 works, but count.set(0) doesn't, then why do we even have the function syntax? 馃槙 We should just use the assignment syntax everywhere instead.
It does work, but it is async.
I think $count = 0; has something like a "auto await tick", because if you change the reset function like this
import { tick } from 'svelte';
async function reset () {
count.set(0)
await tick
console.log('count should be zero ', $count)
}
it works.
svelte store sets are _always_ synchronous
in your examples, the second store is not a store
you're only using the event dispatching feature of the store
the issue is that stores updated by other stores are assumed to be derived stores, so svelte treats them differently
$count = 0 does work because it's known "for sure" that $count will be set to 0, so it sets the value locally at the same time as it calls count.set
set_store_value(count, $count = 0);
that however is willy-nilly compiler optimization that should be removed, as that causes stores such as tweened and spring to be temporally out of sync with the local component value and it only works locally as $count will still be out of sync in other components
the value should be set synchronously anyway so setting it locally at set_store_value before it gets set in component_subscribe is just unnecessary extra work that only hides bad uses of stores such as this one
$count = 0does work because it's known "for sure" that$countwill be set to0, so it sets the value locally at the same time as it callscount.set
OK, that's interesting. Why do we use store.set(value) at all, instead of just assigning stores to their new values like we do with component-scope variables?
in your examples, the second store is not a store
you're only using the event dispatching feature of the store
In fact, in my real-world example the second store _is a store_.
that only hides bad uses of stores such as this one
I need the filter to be a store because it is part of the app state. So, if filter is a store anyway and I need to perform an operation when its value changes, how is it a "bad use" to perform this operation in the subscribe function? 馃
Anyway, the point of this thread is to report an obvious bug and not to discuss code quality.
what if we use a reactive block instead of subscription function, like this...
$: if ($random) {
reset();
}
this feels bit more svelte/reactive...
isn't this same as the subscription function..
mixing sync and async is bad as usual,
either it should just work out of box "somehow"
Or there should be a lint and compiler error for calling sync functions (for those which have async ways using them)
within async function flows...
just a thought..
(it's good i saw this, I'm pretty sure i will waste hours on something like this, try to debug)
@jhwheeler as pushkine said, when you use assignment Svelte assumes that assignment is what you want and sets the store value immediately, regardless of what then happens in set(). Calling set() directly avoids this behavior (might be desirable for custom stores), though frankly I think you'd usually be better off naming the method something else and avoiding the confusion of it also being called _after_ assignment.
I was confused enough by this to ask about it on Stackoverflow. I agree with antony; I would have expected set() and assignment to have the same behavior by default.
@jhwheeler as pushkine said, when you use assignment Svelte assumes that assignment is what you want and sets the store value immediately, regardless of what then happens in
set(). Callingset()directly avoids this behavior (might be desirable for custom stores), though frankly I think you'd usually be better off naming the method something else and avoiding the confusion of it also being called _after_ assignment.I was confused enough by this to ask about it on Stackoverflow. I agree with antony; I would have expected
set()and assignment to have the same behavior by default.
I've just stumbled upon this issue while experimenting with animations. I had my two client sizes (clientWidth and clientHeight) bound to an element and was using their value in a tweened store to animate a different component.
My setup and the assignment using the reactive operator caused the value to update immediately before tweening - like @jwlarocque noted. Here's an example with the red square jumping before animating:
https://svelte.dev/repl/bcc79762e77443f5b9a5bff26c49a57e?version=3.25.0
Here's an example with the store's set method, instead (no jumping):
https://svelte.dev/repl/ce5c48faabec4a1aa771e2c65870d841?version=3.25.0
It would help a lot for beginners if the two behaviours were equivalent (I personally find the assignment's behaviour counter-intuitive) or if there was at least a note in the documentation highlighting this difference.
I've read through this thread a few times, and while I'm not quite sure I understand everything, I'd like to note for the next person who sees this, that if you want to synchronously see the new value reflected,
get(count)
does the trick
console.log('count should be zero ', $count, get(count));
and prints
count should be zero 5 0
https://svelte.dev/repl/be858d84e6364019855d4fb85e2e4c53?version=3.23.2
What's especially encouraging is that derived stores do in fact synchronously reflect their new values when the source stores upstream are set with set no matter whether they're accessed via $derivedStore or via get(derivedStore)
https://svelte.dev/repl/2dca058dd63d4ea39597bc0de6a9bc0e?version=3.23.2
Most helpful comment
@jhwheeler as pushkine said, when you use assignment Svelte assumes that assignment is what you want and sets the store value immediately, regardless of what then happens in
set(). Callingset()directly avoids this behavior (might be desirable for custom stores), though frankly I think you'd usually be better off naming the method something else and avoiding the confusion of it also being called _after_ assignment.I was confused enough by this to ask about it on Stackoverflow. I agree with antony; I would have expected
set()and assignment to have the same behavior by default.