Svelte: Store is not being updated synchronously / store value is incorrect

Created on 22 Jun 2020  路  14Comments  路  Source: sveltejs/svelte

Describe the bug
Store value is not up-to-date if using a $ subscription inside a subscription function.

To Reproduce

  1. Open this REPL.
  2. Open your browser console.
  3. Click on "Increment" a few times.
  4. Click on "Cause reset".
  5. Check your console.

For a real life example look at this REPL.

image

Expected behavior
Value should be in sync.

Severity
Blocking

stores bug

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(). 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.

All 14 comments

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:

  1. Click on "Increment page" a few times.
  2. Click on "Filter".

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 = 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.

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 = 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

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(). 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.

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

robnagler picture robnagler  路  3Comments

angelozehr picture angelozehr  路  3Comments

Rich-Harris picture Rich-Harris  路  3Comments

davidcallanan picture davidcallanan  路  3Comments

thoughtspile picture thoughtspile  路  3Comments