javascriptsveltesvelte-3svelte-transition

Why does adding an if block prevent Svelte transitions from playing?


I'm working on some code where I fetch some items in onMount then render them. I don't want to play a ton of transitions for the big batch of fetched items. However, I do want to play transitions when individual items are added or removed from the list. This is a similar situation to this question.

If I write this, then I get the transition behavior I want.

{#if itemsFetched}
    {#each items as it (it.id)}
        <div transition:slide|local>
            <span>{it.id} {it.name}</span>
            <button on:click={removeItem(it)}>remove</button>
        </div>
    {/each}
{/if}

However, inside of the each block, I need to render the items 1 of 2 ways. For this example, I render even IDs one way and odd IDs another. If I add an if block, then the individual item transitions don't play at all.

{#if itemsFetched}
    {#each items as it (it.id)}
        {#if it.id%2 === 0}
            <div transition:slide|local>
                <span>render like this {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            </div>
        {:else}
            <div transition:slide|local>
                <span>render this other way {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            </div>
        {/if}
    {/each}
{/if}

Here's a full example. Sandbox: https://codesandbox.io/s/crazy-heyrovsky-bwviny?file=/App.svelte

<script>
import { slide } from "svelte/transition";
import { onMount } from "svelte";

// Initially we have no items.
let items = [];
let id = 0;

let itemsFetched = false;
onMount(() => {
    // Fetch items from API.
    items = [
        {id: id, name: `item ${id++}`},
        {id: id, name: `item ${id++}`},
        {id: id, name: `item ${id++}`},
    ];
    itemsFetched = true;
});

function addItem() {
    items = [
        ...items,
        {id: id, name: `item ${id++}`},
    ];
}

function removeItem(rmIt) {
    return () => {
        items = items.filter(it => it.id !== rmIt.id);
    };
}
</script>

<div>
    <button on:click={addItem}>add</button>

    {#if itemsFetched}
        {#each items as it (it.id)}
            {#if it.id%2 === 0}
                <div transition:slide|local>
                    <span>render like this {it.id} {it.name}</span>
                    <button on:click={removeItem(it)}>remove</button>
                </div>
            {:else}
                <div transition:slide|local>
                    <span>render this other way {it.id} {it.name}</span>
                    <button on:click={removeItem(it)}>remove</button>
                </div>
            {/if}
        {/each}
    {/if}
</div>

Why does adding an if block break the local transition? If I remove local, then the items play transitions when added or removed, but then I lose the initial onMount behavior I want.


Solution

  • local restricts the transition to the creation/destruction of the block the element is in, which is the {#if it.id%2 === 0}. But since the ID presumably never changes, the block just always exists, i.e. there is no change and hence no animation is triggered.

    Put the {#if} inside the element with the transition.

    Instead of this:

    {#each items as it (it.id)}
        {#if it.id%2 === 0}
            <div transition:slide|local>
                <span>render like this {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            </div>
        {:else}
            <div transition:slide|local>
                <span>render this other way {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            </div>
        {/if}
    {/each}
    

    Do this:

    {#each items as it (it.id)}
        <div transition:slide|local>
            {#if it.id%2 === 0}
                <span>render like this {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            {:else}
                <span>render this other way {it.id} {it.name}</span>
                <button on:click={removeItem(it)}>remove</button>
            {/if}
        </div>
    {/each}