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.
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.
I've built a quick example where you can see the different behaviors with and without the single-element #each
statement: Svelte REPL
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.
Top comments (2)
Very clever indeed, I faced exactly the same problem (a list, with options to sort and filter items) anf finally supressed the transition.
Still, it feels a bit hacky, I wonder if there isn't a cleaner approach. SOme way to suspend a transition depending on a specific condition.
I've played around with conditional transitions a bit but haven't yet come up with anything that actually works. Next thing I'm going to try is some sort of wrapper function around a transition that can evaluate a function or something to decide to run a transition or not.
Not sure if it'll work but I'll definitely write about it if it does :). If you do come up with something I'd love to hear it!