Reputation: 175
Example and observed behaviour is here: Repl example
I have array of objects, each holding data that is passed to new component in #each loop. Seems like quite standard thing to have.
Each component has a button associated with its state variable, "isDescVisible", which says "show/hide more details". So far, so good. We click "show details" in "first" component, we get details of first component.
But then, we add one more object to data array. Still, pretty common thing to happen. And new component gets rendered. But somehow, state (displaying details or not) of all components (both old and new) is messed up. New component inherits state from old "top" one, and so on.
Question: why? second question: how to avoid that?
I could have imagined, that after mutating array, all components are destroyed and re-rendered. But in that case, we should have not see details displayed for any of those.
For those that don't want to go to repl, code below:
App.svelte:
<script>
import Entry from './Entry.svelte'
let base = [
{title: 'First title', desc: 'First description'},
{title: 'Second title', desc: 'Second description'}
]
function updateBase() {
const newEntry = {title: 'Third title', desc: 'Third description'}
base = [newEntry, ...base]
}
</script>
<button on:click={updateBase}>Add me!</button>
{#each base as entry}
<Entry {entry} />
{/each}
Entry.svelte:
<script>
export let entry
let isDescVisible = false
</script>
<p>
{entry.title}
</p>
<button on:click={()=> isDescVisible = true}>
Show Description
</button>
{#if isDescVisible}
<h2>
I am visible! My description is {entry.desc}
</h2>
{/if}
Upvotes: 0
Views: 4480
Reputation: 5492
Give each item an ID and use it as the key in the #each
block:
<script>
import Entry from './Entry.svelte'
let base = [
{title: 'First title', desc: 'First description', id: 1},
{title: 'Second title', desc: 'Second description', id: 2}
]
function updateBase() {
const newEntry = {title: 'Third title', desc: 'Third description', id: 3}
base = [newEntry, ...base]
}
</script>
<button on:click={updateBase}>Add me!</button>
<!-- The each loop uses the property in parentheses as the key -->
{#each base as entry (entry.id)}
<Entry {entry} />
{/each}
By providing a key, you are helping Svelte figure out which DOM node to change when base
is updated. Without a key, Svelte will add and remove items at the end of the list by default, and then update any values that have changed. So we start with this...
... and we add an item to the array, so we want this...
Without the key, Svelte doesn't know that we're adding an item to the front of the list. That's just one way to get to the final state. Another way (and the one Svelte defaults to) is to update (1)'s title to be "Third title", update (2)'s title to be "First title", and add a new item that has "Second title". Because (1) only has the props updated and is not recreated, it's internal open/closed state is preserved.
By adding a key, Svelte knows that the item at the front of the list is a new item, and will create a new node there instead of re-using the old one.
Tan Li Hau has a good visual explanation of this on Twitter.
tl;dr You should always provide a key in an #each
loop if you are going to be modifying the list in any way.
Upvotes: 5