Reputation: 4558
I don't understand this section in the tutorial: https://svelte.dev/tutorial/keyed-each-blocks.
I can see the things
array is updated correctly so the right thing.color
is passed as expected. But by the first sentence "By default, when you modify the value of an each
block, it will add and remove items at the end of the block, and update any values that have changed.", it seems to be saying that Svelte anyway removes the last block when clicking the button, then the remaining 4 blocks will be faced with the sliced things
, which is
[{ id: 2, color: '#6a00a8' },
{ id: 3, color: '#b12a90' },
{ id: 4, color: '#e16462' },
{ id: 5, color: '#fca636' }]
And since initial
is declared as const
, it cannot be updated anymore, so the colors of thing.id
1--4 remains.
Is this a correct understanding? Is this default behavior assuming the each
blocks are exchangeable?
Then it says using thing.id
as the key for the each
blocks will solve the issue, namely, {#each things as thing (thing.id)}
. I don't understand how the keys are used in the each
blocks and what was the default key if thing.id
is not provided. And why the default key (if there is one, or the default no-key) doesn't work while providing thing.id
does.
Thanks for the clarification.
Upvotes: 35
Views: 18088
Reputation: 69
Whenever you are using nested components inside {#each} {/each}
, if there are multiple components present, svelte need a way to identify each individually. It does so by assigning them each a key internally. If you want to manipulate those components, you can explicitly assign them keys so that you can identify them.
What is happening there is as no key is explicitly defined, it counts the size of 'things' and showing them. whenever you reduce the size of 'things' it again reiterates from top to the size of 'things' instead of choosing the respective <Thing>
as you have not bind them. You need to bind the <Thing>
with something from 'things' element so that you can manipulate as per the value of 'things'
element.
In the example,
(thing.id)
is used as key to identify. you can use something like (thing.hashCode())
too and the result would be same. Anything that results in unique identification and bind to 'things' will do.
Upvotes: 1
Reputation: 81
I was struggling with this example also, why the icon isn't changed. I noticed that values computed in component after initalization (const emoji = emojis[name]
) stayed the same, but if we use reactive declarations: $: emoji = emojis[name];
value will be recalculated and this example works fine.
Upvotes: 4
Reputation: 63
Had the same doubt, then I realized what "it will add and remove items at the end of the block" may means here probably applies to the DOM, so even if you remove the first item of an array in JavaScript, Svelte always removes the last DOM node. With key provided, both DOM and JavaScript can act the same.
Upvotes: 1
Reputation: 1199
The API docs explain it thus:
If a key expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
{#each items as item (item.id)} <li>{item.name} x {item.qty}</li> {/each} <!-- or with additional index value --> {#each items as item, i (item.id)} <li>{i + 1}: {item.name} x {item.qty}</li> {/each}
I found this was lacking proper explanation in the tutorial as well. However, I think the docs are more clear-- a unique key may be provided to the each function so that every iteration is uniquely identified. Thus, when a particular element is removed from the data provided to the each function, the correct iteration can be identified and removed.
Upvotes: 4
Reputation: 712
I believe it uses something like the index of the item as the default when you do not provide a key. This can be validated by using
{#each things as thing, index (index)}
<Thing current={thing.color}/>
{/each}
which gives the same behavior as not using a key in the first place.
Let's call the <Thing>
that was rendered for id: 1
as Thing1
, and so on.
When we remove the first item from the list, Thing1
still stays the same, because the key (index in this case) associated with it remains the same. The prop that was earlier being sent to Thing2
is now being sent to Thing1
. This happens all the way down the chain. But now that there is one less element, Thing5
is removed from the DOM.
The instance of the component Thing
that is associated with the key "0" (Thing1
) is not destroyed when the first item is removed. This happens because the key stays the same (the new array also has an item at index 0). Only the prop that is being sent to Thing1
has changed, leaving the initial
variable with the color of the original item with id: 1
.
When the thing with id: 1
is removed, there does not exist any instance of Thing
which maps to "1". So, Thing1
is removed from the DOM.
Another way to understand is when you give a key, you are essentially telling Svelte to map each rendered block to that very key. When that key doesn't exist anymore, get rid of the block and remove it from the DOM. But if the key stays the same and the props change, update the props instead of recreating the block.
When you don't specify a key, it uses the index of the list as the key. So if you remove items from the list, it won't recreate or reorder the block, it will just update the props.
Upvotes: 49