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

$derived doesn't update derived values when used with a store containing an object. #14772

Open
tomconnors opened this issue Dec 19, 2024 · 10 comments

Comments

@tomconnors
Copy link

Describe the bug

When using $derived to get the value from a store containing an object, updates to the stored value don't cause the derived value to update. I suspect this is not by design, because $effect does handle those updates appropriately. If this is a known limitation, perhaps it should be mentioned in the docs.

Can work around by copying the object with Object.assign.

// This doesn't work:
let x = $derived($myObjectStore);
// This works:
let x = $derived(Object.assign({}, $myObjectStore));

Reproduction

https://svelte.dev/playground/hello-world?version=5.14.4#H4sIAAAAAAAAE41Sy26DMBD8lZUVKUZBpL2SEKnqpf9QeiBm09KaNbKXJBXyv1eGhIQ-oh49s-P1zLgTVNQoUvGEWhs4GKtLkFhWjGUkYrGrNDqRPneCP5swFwARn1UPTZO4PWoO2LZw-BuuDDESO5GKtVO2aniTU85V3RjL8GhaYrSws6aGebI8nU_y-Sqn9fKiovV5frm5dRCxYDyySNm26OM_nj_dNbXwg7ttowMVBOBHI-6tsFgm7y54yFkjg4IMZiXaao-lnPWCaCTpmlQJXZj6miFYwP3AzXC3Q8VSRtmmC0DOypAzGhNtXmUuhhW5iOG0LAz56Huk25bZEBhSulIfWScjyDaDn6RtyoJRShN2gElosViBRW4tgVn5yPch9EKXQkc-hq72YcNw67-7GOOa1nANXzUwpn6wFRdbjWPwQ19Lx8b2vycnPPazfQF9R9mokh2lcOdDnNNXvvgvf8s4ARoDAAA=

Logs

No response

System Info

System:
    OS: macOS 15.0
    CPU: (8) x64 Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz
    Memory: 36.44 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.12.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.9.0 - /usr/local/bin/npm
    pnpm: 8.0.0 - /usr/local/bin/pnpm
  Browsers:
    Chrome: 131.0.6778.204
    Safari: 18.0
  npmPackages:
    svelte: 5.14.4 => 5.14.4

Severity

annoyance

@trueadm
Copy link
Contributor

trueadm commented Dec 19, 2024

This isn't a bug. $derived only triggers updates if the value it is returning has changed in === equality since it last ran. In this case the store object is the exact same each time, so it doesn't propagate any changes.

Instead, you should return the value you care about, or do what you're doing now and create a new value:

let c = $derived($count.n);

@webJose
Copy link
Contributor

webJose commented Dec 19, 2024

Interesting. I think the confusing part is that by calling the store's update() method, it is expected that subscribers receive the update, even if the object didn't change.

@trueadm
Copy link
Contributor

trueadm commented Dec 19, 2024

@webJose The derived does receive the update – however deriveds are memoized on the value being different, so it doesn't further pass on changes as it doesn't think anything has changed.

@webJose
Copy link
Contributor

webJose commented Dec 19, 2024

I see. I think this is the behavior that has been affecting me for some weeks now. We have micro-frontends in Svelte v5. One of them has the UI for users to control the settings of the glass effect used in floating stuff like menus. The other MFE's consume this setting, which is in a $state() object that is also saved to local storage. This $state() is wrapped in a class, and said class instance (a singleton) is made available for consumption by other MFE's. So all MFE's use the same store.

Yes, the MFE's that are not the creators of the singleton cannot receive updates via the signal because the signal belongs to another copy of the runtime, so we made it also implements the Readable contract.

Enabling/disabling glass triggers reactivity in the MFE's instantaneously, but changes in blur, opacity or saturation do not. I suppose my expectation is that subscribers are executed and that a change should trigger, regardless.

@trueadm
Copy link
Contributor

trueadm commented Dec 19, 2024

You can always be inventive here, such as wrapping the value in an object that always changes on a propagation of a change.

let x = $derived({ current: $myObjectStore });

// …
x.current

@tomconnors
Copy link
Author

Thanks @trueadm, the memoization explanation makes sense. I guess $effect does not memoize, which would explain the different behavior?

I think the "Stores" part of the svelte 5 docs should include a section that mentions 1) $derived works with stores, 2) An updated store will not necessarily trigger $derived updates if the value is === the previous, and 3) You can deal with that using the techniques you listed.

@webJose
Copy link
Contributor

webJose commented Dec 19, 2024

Ha! Spreading also works. For this issue, do:

let c = $derived({...$count});

This is what I did in my MFE setup and works. So does it work in your REPL.

@7nik
Copy link

7nik commented Dec 19, 2024

let x = $derived($myObjectStore);

Why? There is fromStore which works flawlessly.

@tomconnors
Copy link
Author

fromStore has the same "problem" that $derived($store) has - if the object is mutated rather than replaced in the store.update call, and your $derived call just gets that top-level object, the derived value will not update. I'm also seeing the $derived({ current: $myObjectStore }); solution fail, but I don't know why since that is a different object every time the store updates.

Specifically I'm seeing:

// works:
let c = $derived(Object.assign({}, $count));

// works:
let c = $derived({...$count});

// works:
let obj = fromStore(count);
let n = $derived(obj.current.n);

// doesn't work:
let {c} = $derived({c: $count});

// doesn't work:
let obj = $derived({current: $count});
let c = $derived(obj.current);

// doesn't work:
let obj = fromStore(count);
let c = $derived(obj.current);

Here's a playground with these examples.

@trueadm
Copy link
Contributor

trueadm commented Dec 19, 2024

They won't work, because you reference the store object again, which hasn't changed still. If you need to create a derived from a derived then you'll need to spread the object.

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

4 participants