Search code examples
svelte

How to triger $effect() with bound variables in svelte?


I have a variable that is correctly bound to its parent: its value changes on click, and that value is correctly displayed in both parent and bound child. You can test that in svelte playground: https://svelte.dev/playground/c2fea05c50394c1199d31aa525df5418?version=5.19.10

+page.svelte

<script lang='ts'>
    import Foo from './Foo.svelte'
    
    let page = $state({value:12});  
    
    $effect(() => {
    console.log("Updating page: ", $inspect(page));
  });
</script>

<div>
  <Foo bind:page={page} />
    App page is {page.value}
</div>

Foo.svelte (child)

<script>    
    let {page =$bindable()} = $props();

</script>

<button onclick={()=>page.value = page.value+1}>
    Value in Foo is {page.value}: click me
</button>
<br/>

Despite the rerendering being happening, my $effect() that uses the same changing variable doesn't get called. How can I detect that a change happened in my page (the idea, is that I developed a ts file that automatically saves data on change, and I cannot find a way to get notified of changes.

p.s: saving page in a store would also need me to detect that a change happened, and since my object has dozens of fields, I don't want to manually trigger an event on change. Any ideas on how to do that?

Solution based on brunnerh answer:

"just referencing the object will not trigger anything (unless a reassignment of the variable happens)."

And said, let's try something stupid (because, if the method is not called, then that should never happen right?). But it actually solved the issue, as the effect is considered worth calling now:

$effect(() => {
    console.log("Updating page: ", $inspect(page));
    autoSave.update(page = {...page})
});

It is still very counterintuitive though, I do not find this explicit enough. A handle to say "force effect even if not writes are happening" would be better understood I guess


Solution

  • The $effect will be called if the property being modified is actually used. In Svelte 5, $state is very granular, so just referencing the object will not trigger anything (unless a reassignment of the variable happens).

    So as soon as you actually serialize the data to store it, the $effect will trigger as it is supposed to.

    $effect only tracks dependencies synchronously. If you know that some asynchrony will break the flow later, you can evaluate the object up front to track it regardless.

    For getting the current... state of a $state object, you can use $state.snapshot. Otherwise the variable will contain a Proxy, which may not work with certain APIs. This will also deeply evaluate the object (which is not necessarily a cheap operation)

    $effect(() => {
       somethingAsync($state.snapshot(page));
    });