DEV Community

Cover image for Suppressing Svelte transitions with the #each hack
Daniel Imfeld
Daniel Imfeld

Posted on • Originally published at imfeld.dev on

Suppressing 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.

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
opensas profile image
opensas

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.

Collapse
 
dimfeld profile image
Daniel Imfeld • Edited

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!