sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.22k stars 4.17k forks source link

Svelte 5: Unpredictable behavior of $effect statements #13813

Open vhtmui opened 13 hours ago

vhtmui commented 13 hours ago

Describe the bug

The $effect rune behaves differently when variables are declared in different places.

Consider this code:

<script>
    let {display = true} = $props();//case 1
    // let display = $state(true); //case 2

    let Istrue = true;

  function toggle_display() {
        display = !display
    }

// this run everytime click the button if  `display` is in the $props statement.
    $effect(()=>{
        if (Istrue){
            display = true;
            alert("alert from $effect!");
        }
    })
</script>

<button onclick={toggle_display} >display</button>

{#if display && true}
    <h1>Hello world</h1>
{/if}

In case 1, display declaration is in the $props statement, this will cause the $effect statement runs everytime i click the button. In case 2, display declaration as a reactive varriable, the $effect statement will not runs one time.

From my perspective, $effect should only run when the value of the if statement changes, which is counterintuitive right now. I'm not sure if it's a feature or a bug, but I hope $effect's behavior to be consistent in this two case. Because this work well in svelte 4 with afterUpdate, it seems that $effect cannot replace afterUpdate.

Reproduction

https://svelte.dev/playground/8507880e5cb74c13a4d6dd67e645092a?version=5.0.5

Logs

No response

System Info

nothing

Severity

annoyance

petermakeswebsites commented 12 hours ago

There's some unnecessary code in your example, so I just cleaned it up while preserving your main question:

<script>
    let {display = true} = $props(); //case 1 - fires every time you press display
    // let display = $state(true); //case 2 - fires once at the start

    $effect(()=>{
        display = true;
        console.log("effect")
    })
</script>

<button onclick={() => display = !display} >display</button>

{#if display}
    <h1>Hello world</h1>
{/if}

This is a bit of an anti-pattern anyway. Generally speaking you should only really be using $effect for the edges of your application e.g. hitting an API point or something of the sort. For everything else, $deriveds, and more often than not just functions that call some stateful value, are all that's required and is the fastest, most efficient, and safest way to do things. I actually made a a video on this a while back that explains why.

Nevertheless, I'm actually a bit bamboozled myself by this. Even though it is an anti-pattern, I don't really see why $effect should be behaving differently, so I'm curious why this would be the case.

I would expect the effect to only be called once at the start since within the effect, the display value at all should be being accessed at all, which is what's needed to set up the reactivity relationship. So that particular effect should not be reactive to anything... I think ?