Svelte store assignment calls default writable().set and then custom .set?
Asked Answered
H

3

6

Relevant REPL

I have a simple writable store with a custom set method:

import { writable } from 'svelte/store';
function createState() {
    const {subscribe, set, update} = writable({a: 0, b: 0});
    return {
        subscribe,
        set: (newState) => {
            console.log(newState);
            // set(newState); // I would expect `state` to be unchanged without this
        }
    };
};

export const state = createState();

When I call state.set(<some new value>), the new value is logged to the console and the value of state does not actually change. This is what I expect.
However, if I assign $state = <some new value>, the value of state changes, and then set logs it to the console. Why (and how) does this happen, and is there a way around it without reimplementing writable?

Thanks!

Humphries answered 22/7, 2020 at 6:2 Comment(0)
S
5

This is expected behaviour! Forget about stores for a moment, and — since state.set is a noop in your example — remove that line:

<script>
    // import { state } from "./stores.js";

    let pojo = { a: 0, b: 0 };
    pojo.a = 1;
    pojo = {d: 0};

    //state.set({c: 0}); // no effect, as expected
</script>

<h1>state: {JSON.stringify(pojo)}</h1> 

The outcome...

state: {"d":0}

...is exactly what you'd expect.

When we replace pojo with $state, we're still assigning to (or mutating) a local variable, with the same reactivity characteristics. The only difference is that we're also calling state.set. Ordinarily that would cause a change in the store value that would be reflected to its subscribers, but since your custom store doesn't do that, we're left with the value following the last time $state was touched by an assignment operator.

Sparks answered 23/7, 2020 at 18:48 Comment(2)
Thanks very much for the explanation! I've accepted this answer because it explains why this is the behavior. If anyone comes across this question and wants to know more about what is happening, Thomas and Andreas provided some excellent insight there as well. (Incidentally, I originally asked this question because I wanted updating my store to have side effects instead of/before assignment. It seems like that would not be Svelteish, and so I've restructured my app so that logic is moved elsewhere.)Humphries
The explanation turns out to be a lot simpler than I expected, and yet very logical. Thanks Rich!Negrete
N
4

In the JS output of your REPL, you can see the following:

function instance($$self, $$props, $$invalidate) {
    let $state;
    component_subscribe($$self, state, $$value => $$invalidate(0, $state = $$value));
    set_store_value(state, $state.a = 1, $state); // changes the value of state?!
    set_store_value(state, $state = { d: 0 }); // ''
    state.set({ c: 0 }); // no effect, as expected
    return [$state];
}

Using the shorthand reactive assignment $<store> compiles into set_store_value() calls, which is a svelte internal method defined thusly:

export function set_store_value(store, ret, value = ret) {
    store.set(value);
    return ret;
}

So the assigned value is, in effect, passed on to your store's set function, as you would expect.

However, you can see in the JS output above that the value ultimately returned is a local variable called $state (the $ sign there is not a reactive modifier, just part of the name). During those set_store_value() calls, you can see that this local variable is assigned the same value that is passed on to your store's set method (in fact, the local variable is assigned that value, and then is itself passed on to the set_store_value() method):

set_store_value(state, $state.a = 1, $state); // changes the value of state?!
set_store_value(state, $state = { d: 0 }); // ''

I expect this behavior to be some sort of optimistic look-ahead/resolution.

Perhaps it is implied in the Svelte store contract that a value passed to a store's set method must in fact modify the store accordingly, in which case the optimistic resolution approach would always yield coherent results?

Hopefully Rich Harris (or another Svelte contributor) will see your question and provide a more definitive answer.

Negrete answered 22/7, 2020 at 11:59 Comment(0)
L
1

Expanding on Thomas Hennes great analysis:

set_store_value(state, $state.a = 1, $state);

This internal call updates the internal store model ($state.a = 1) without using the set or update method. To me, this is a bit unexpected and probably a svelte bug. At least, it's something that we need to keep in mind when we design sophisticated custom stores and want to use the store shorthands.

Loralyn answered 22/7, 2020 at 12:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.