Suppress Svelte transitions with the #each hack
A common question I’ve seen in the Svelte Discord is how to force a component to recreate itself when some of its input data changes. I think this is usually an anti-pattern, but the technique to make it work does have some other interesting uses.
Update! 🔗
As of Svelte 3.28, this sort of syntax is officially supported with the {#key}
syntax:
{#key expression}
<SomeComponent />
{/key}
With this syntax, SomeComponent
will be torn down and recreated every time the expression changes. Just like the single-element #each
below, but simpler to understand.
I’ve left the rest of the article below unchanged for now. The underlying techniques are the same, but the syntax is a lot nicer.
Single-element keyed #each 🔗
Svelte’s #each
statement allows you to specify a key for each item in an array. When the array updates, Svelte can then use this key to match items in the old array to the items in the new array, and see what changed.
Using a single-element array and a keyed #each
statement, we can force Svelte into recreating elements instead of performing a partial update:
{#each [key] as k (k)}
<SomeComponent />
{/each}
Now any time the value of key
changes, SomeComponent
will be completely torn down and reconstructed. While there are occasional good reasons to do this, I prefer to avoid it as much as possible, and instead just make components react to changes in their properties.
Suppressing Transitions 🔗
That said, I did discover a good use for the single-element #each
while working on a new referrals analysis view for Carevoyance. One portion of this view allows the user to maintain a list of interesting physicians, and the animate:flip
and transition:fade
directives provide nice animations when physicians are added to and removed from the list.
The original code, vastly simplified, looked something like this:
{#each list.people as person (person.name) }
<div animate:flip={{duration: 500}} transition:fade|local={{duration: 500}}>
{person.name}
</div>
{/each}
After the initial implementation, I updated the code to support creating multiple lists and switching between them. Now, the transitions still ran when switching between lists, and it did not look good.
Most of the rows faded in and out, while physicians who were present in both lists slid around to their new positions. It felt slow and disconcerting, so I wanted to bypass the transitions when switching lists.
This is where the single-element keyed #each
comes in. We can still get the nice transitions when updating list.people
, but reassigning list
to point to an entirely different list recreates the entire block and skips the transitions.
{#each [list] as l (l.id)}
<div>
{#each list.people as person (person.name) }
<div animate:flip={{duration: 500}} transition:fade|local={{duration: 500}}>
{person.name}
</div>
{/each}
</div>
{/each}
The |local
modifier on the fade transition is important here. It tells the fade to run only if the template block it belongs to (here, the #each list.people
block) is changing, not if one of the parent blocks (the #each [list]
block) is changing.
Here’s a quick example where you can see the different behaviors with and without the single-element #each
statement.
And that’s it! This isn’t something that comes up too often, but it’s a nice trick to have when you need it.