Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Svelte #each block update condition

When having an #each block in svelte (like https://learn.svelte.dev/tutorial/keyed-each-blocks), the entry is only updated if the content changed. This works perfectly in the tutorials example, where a string is given as property to a nested component:

{#each things as thing (thing.id)}
    <Thing name={thing.name}/>
{/each}

But if I give the whole object (thing) and adjust Thing accordingly, it always updates all list entries. Hence I wonder what the condition is Svelte decides on, whether to update the component or not. Is it the property, which is incase of the whole object a reference and therefore always changes? Or is the whole Nested component generated to be compared against the DOM? Is it bad practice to give an Object to a component?

App.svelte

<script>
    import Thing from './Thing.svelte';

    let things = [
        { id: 1, name: 'apple' },
        { id: 2, name: 'banana' },
        { id: 3, name: 'carrot' },
        { id: 4, name: 'doughnut' },
        { id: 5, name: 'egg' }
    ];

    function handleClick() {
        things = things.slice(1);
    }
</script>

<button on:click={handleClick}>
    Remove first thing
</button>

{#each things as thing (thing.id)}
    <Thing name={thing} />
{/each}

Thing.svelte

<script>
    import {
        beforeUpdate,
        afterUpdate
    } from 'svelte';
    
    const emojis = {
        apple: '🍎',
        banana: '🍌',
        carrot: '🥕',
        doughnut: '🍩',
        egg: '🥚'
    };

    export let name;
    const emoji = emojis[name.name];

    beforeUpdate(() => {
        console.log('before updating ' + name.id)
    });
    
    afterUpdate(() => {
        console.log('after updating ' + name.id)
    });
</script>

<p>{emoji} = {name.name}</p>

The update lifecycle functions are called everytime, even if the content didn't change.

Edit: With the REPL there is this JS output tab which I searched a little. There are many of these p() {...} like:

    p(ctx, [dirty]) {
        if (dirty & /*name*/ 1 && t2_value !== (t2_value = /*name*/  ctx[0].name + "")) set_data(t2, t2_value);
        },

which seem to do the job. The one above is the one from the Thing create_fragment return. To me, the comparison seems good, but still an update is done.

like image 257
Lothar Avatar asked Sep 13 '25 21:09

Lothar


1 Answers

You have a couple of questions in your post, so to best help, I'll break them down into separate parts. I hope this clears things up at least a little bit.

Part 1: Why does the component update when I pass in the whole object?

Is it the property, which is incase of the whole object a reference and therefore always changes? Or is the whole Nested component generated to be compared against the DOM?

It sounds like you are on the right track. This is how Svelte's reactive behavior works. It will trigger an update when the props are determined as changed; however, it does not do a deep equality check. So in your example, you are passing in a whole object, so the reference to the object will be used to determine if the prop has changed.

In the {#each} block, you have set {thing.id} as the key. This means that Svelte will follow these rules (for the most part) to determine whether it should rerender the component:

  1. The object is new (it was not on the list before).
  2. The object with the same key (thing.id) has been removed from the list.
  3. The key thing.id changes.

So here comes the tricky part. Even though you are only slicing out part of the array in handleClick() Svelte will still update the component since it sees that the reference has changed.

Note: Creating a new array with new references is the behavior of slice.

To get around this, you could pass the component a specific property of the object rather than the entire thing:

{#each things as thing (thing.id)}
    <Thing name={thing.name}/>
{/each}

Part 2: Is giving an object to a component bad practice?

Is it bad practice to give an Object to a component?

This is a bit subjective, but in my opinion, passing an object is not necessarily a bad practice, but you should be aware of the implications. A couple of high-level points off the top of my head might be:

  1. If the object is large or changes often, this could lead to many unnecessary updates and performance problems.
  2. If only specific properties of the object are used by the component, passing only those properties could improve performance and make the code more transparent.

But like I said, this is only my opinion.

Part 3: p(ctx, [dirty]) and what is going on here

The update lifecycle functions are called every time, even if the content didn't change.

As I'm sure you know, this is a part of Svelte's compiled code. This checks if the name prop in the ctx object (current context of the component) has changed, and if so, it will update the text content of the corresponding DOM element. In your case, the name prop is an object, and every time you slice the array, that object's reference changes, so this function will always consider it as "changed".

like image 159
PCDSandwichMan Avatar answered Sep 16 '25 11:09

PCDSandwichMan