Martin J.H.
Martin J.H.

Reputation: 2205

Svelte: using "bind:this" inside "each" block

I want to render an array of objects, using an each block. I need to be able to remove elements from the array using callbacks from inside the each block. Also, for more complex UI-interactions, I need the reference to each component of the each-block to be available in the main app.

Here is my approach:

<script>
    let items = [{text: "one"}, {text: "two"}];
    
    function deleteItem(i) {
        items.splice(i, 1);
        items = items;
    }
</script>

{#each items as item, i}
<button bind:this={items[i].ref} on:click={() => deleteItem(i)} >
    {item.text}
</button>   
<p />
{/each}

Understandibly, this leads to errors such as item[i] is undefined, because when the splicing of items is processed, the bind:this cannot properly be cleaned up anymore.

I tried to solve this by moving the component-references to a separate array. But no matter what I try, I cannot get the reference-array and the object-array to sync: Whenever deleteItem() is processed, I end up with null-values inside the refs-array. Here is one of my approaches (the each-section that prints the refs-array should help to show the null-values):

<script>
    import {tick } from "svelte";
    let items = [{text: "one", id: 1}, {text: "two", id:2}];
    let refs = [];
    
    async function deleteItem(i) {
        items.splice(i, 1);
        await tick();
        refs.splice(i, 1);
        items = items;
        console.log(refs);
    }
</script>

{#each items as item, i (item.id)}
<button on:click={async () => deleteItem(i)} bind:this={refs[i]}>
    {item.text}
</button>   
<p />
{/each}

{#each refs as ref}
{ref}
<p />
{/each}

I tried with and without tick(), tried inserting the tick() in different places, with and without async and with and without using (item.id) in the each block. How can I keep the references and the data in sync?

Upvotes: 10

Views: 10427

Answers (1)

Stephane Vanraes
Stephane Vanraes

Reputation: 16411

On way to get around this is to have the refs array cleaned up before you use it:

<script>
 let items = [...]
 let _refs = []
 $: refs = _refs.filter(Boolean)
</script>

<button bind:this={_refs[i]}></button>

With this technique you will still have the null values in the original _refs array, but the copied version (refs) will be clean.

Another way is to bind the element to the item itself, but this might not be desired if you use that data elsewhere as well:

<button bind:this={item.ref}>

(note that this will still give errors because the bound element disappears during the slice but is only processed with the assignment, you can get around that by doing the slicing and the assignment in one using a filter statement)

items = items.filter((item, idx) => idx != i)

Upvotes: 12

Related Questions