sveltesvelte-5

Svelte $effect rune not logging on array.push for $state rune


This is the code I'm using. Its also in sevelte5 playground here. What I don't understand is, why it's not logging when I add todo with array.push but works when I do reassignment. However, when I uncomment commented lines in $effect body, it works for both. Just curios if I understand $effect correctly here.

<script>
    let todos = $state([{
        text: "Todo 1",
        status: "pending"
    }]);

    $effect(() => {
        console.log(todos)
        // const countEl = document.getElementById("count");
        // countEl.innerHTML = todos.length
    });

    function pushTodo(event) {
        if (event.key != 'Enter')return
        const elem = event.target
        todos.push({text: elem.value, status: "pending"})
        elem.value = ""
    }
    function spreadTodo(event) {
        if (event.key != 'Enter')return
        const elem = event.target
        todos = [...todos, {text: elem.value, status: "pending"}]
        elem.value = ""
    }
    
</script>

<div class="todos">
    <input onkeyup={pushTodo} placeholder="Push Todo">
    <input onkeyup={spreadTodo} placeholder="Spread Todo">
    <ul>
    {#each todos as todo}
        <li>
            {todo.text}: {todo.status}
        </li>
    {/each}
    </ul>
    <p>Total: <span  id="count"></span></p>
    
</div>

Solution

  • Reactivity in Svelte 5 is fine-grained. If you have an effect that just references an array, that effect will only trigger if the array reference changes, i.e. on reassignment to a different array.

    This also means that dummy assignments like todos = todos will not do anything.

    If you just want to observe an object for development purposes, use $inspect, which observes the object deeply and also unwraps the state proxy. (Note that $inspect will not be part of the production build.)

    If the $effect actually uses any of the array contents, it will also trigger accordingly. E.g. if you JSON.stringify it, all items and their properties are read and hence observed by the effect.

    $effect(() => {
        console.log(JSON.stringify(todos))
    });
    

    push() affects todos.length, so if you read that in the $effect, it will trigger on push() but not if an existing item is modified.

    $inspect internally uses $state.snapshot which also can be used in an effect to deeply read the object.