Reputation: 2205
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
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