Understanding Svelte's Each Block for Efficient List Rendering

Understanding Svelte's Each Block for Efficient List Rendering

When I first started learning Svelte, I was amazed by its simplicity and efficiency. It felt like magic — no virtual DOM, faster updates, and a reactive system that just worked. But then, I ran into something that seemed... well, a bit strange at first.

I was playing around with an each block to loop through a list of items, and I noticed something unexpected: when I removed an item from the list, the last item visually disappeared, even though I was removing the first one. What was going on?

If you’re coming from other frameworks, you might expect the entire component to re-render when state changes, but in Svelte, things work differently. Svelte updates the DOM in a fine-grained way, which is great for performance — but it can sometimes lead to surprising behavior. Let’s dive into it!

The Problem: Fine-Grained DOM Updates

Imagine you have a Thing component that takes two props:

  • name: a dynamic prop that changes based on your data.

  • emoji: a constant prop that doesn’t change.

Here’s a simple Svelte app:

<script>
  let things = [
    { id: 1, name: 'Thing 1', emoji: '😀' },
    { id: 2, name: 'Thing 2', emoji: '😂' },
    { id: 3, name: 'Thing 3', emoji: '😍' }
  ];

  function removeFirst() {
    things = things.slice(1); // Removes the first item
  }
</script>

<button on:click={removeFirst}>Remove first thing</button>

{#each things as thing}
  <Thing name={thing.name} emoji={thing.emoji} />
{/each}

You’d expect the first Thing to disappear when you click the button, right? But here’s what actually happens:

  1. It removes the last component (visually).

  2. The name props update for the remaining components, but the emoji props stay the same.

Wait, what?!

This happens because Svelte reuses the DOM nodes for efficiency. Instead of destroying and recreating components, it simply updates the properties that change. Since emoji is constant and doesn’t reactively change, Svelte leaves it as is. The result? The last item disappears, and the rest get updated.

If you’re used to other frameworks like React, this might feel odd. In React, the component tree re-renders based on changes in state, so this problem doesn’t happen. But Svelte takes a more surgical approach to DOM updates, which is faster and smarter — but sometimes too smart for its own good!

The Solution: Using Keys in Each Blocks

To fix this, we need to tell Svelte to treat each item in the each block as unique and not reuse the DOM nodes. We do this by providing a key for each item:

{#each things as thing (thing.id)}
  <Thing name={thing.name} emoji={thing.emoji} />
{/each}

Here, (thing.id) is the unique key. It tells Svelte:

  • “Hey, each Thing component is tied to a specific id, so if something changes, destroy and recreate only what’s necessary.”

Now, when you remove the first item, Svelte:

  1. Destroys the DOM node for the removed item.

  2. Recreates the remaining nodes with their correct props.

Problem solved! 🎉

Keys Can Be Any Object

Svelte uses a Map internally for keys, so you can technically use any object as the key, like (thing) instead of (thing.id). However, using a string or number is generally safer. This is because strings and numbers maintain identity without relying on referential equality.

For example, when updating your list with fresh data from an API server, keys like id will persist correctly, even if the object references themselves are new.

Why Does This Matter?

At first, it might seem like a small thing, but keys are super important when working with dynamic lists. Here are a few takeaways:

  1. DOM Node Reuse: Svelte reuses DOM nodes to make updates lightning fast, but this can lead to weird behavior when you’re not expecting it.

  2. Keys = Control: By providing a unique key, you tell Svelte how to identify and update items in a list.

  3. Use Strings or Numbers for Safety: While objects can work as keys, strings or numbers ensure identity persists when working with fresh data.

  4. Performance-Friendly: Unlike React, where entire components re-render, Svelte’s fine-grained updates are more efficient, and keys help you make the most of this optimization.

Final Thoughts: Embracing the Svelte Mindset

Learning Svelte has been a breath of fresh air. It does things differently, but once you understand how it works, you realize just how powerful and efficient it is. This each block behavior taught me an important lesson: Svelte trusts you to be explicit about how you want updates to happen.

By adding keys, you gain more control over your components while keeping your app fast and lightweight. So the next time you’re looping through a list and something seems off, remember: just add a key!

Happy coding! 🚀

TL;DR:

  • Svelte reuses DOM nodes in each blocks for efficiency.

  • If you remove or modify items, constant props might behave unexpectedly.

  • Add a unique key like (thing.id) to ensure items are treated correctly.

  • Keys = fine-grained control + better performance!

  • Use strings or numbers as keys for safer updates, especially with fresh data from APIs.