Svelte {#each} Blocks - You Should Know These Tricks
| Published: January 05, 2024
| Updated: January 18, 2024
If you want to iterate through an array in the markup of a Svelte component, you’ll need to use the {#each}
block. As a block to unique to Svelte, it’s something you’ll need to learn and understand coming from any other framework. You’ll use it a lot!
{#each}
Blocks - The Basics
The syntax is simple. The most expansible version of the opening {#each}
statement follows the basic format
{#each array as item, index (key)}
<!-- Do something -->
{/each}
Where key
and index
are optional (however including a key
value is recommended if the array will be mutated).
For a shopping list app, you can display the items in the list using the following:
<script>
const list = ['Apples', 'Lettuce', 'Milk'];
</script>
{#each list as item}
<p>{item}</p>
{/each}
For any developer worth their salt, it’s not exactly rocket science.
But, in the real world, many situations aren’t as straightforward as the example above.
This article will walk through some of the quirks and limitations of the Svelte {#each}
block and how to resolve them. I’ll also present some Easter eggs that you might not be aware of that will level up your workflow when working with arrays in your Svelte markup.
Quirks and Limitations
Quirk #1: Typescript
Svelte unfortunately doesn’t allow for typescript syntax inside of the markup. Therefore, when using type assertions, or non-null assertions, the Svelte compiler say “no 😠” .
Lets take this example - we want to pass in some array, which contains elements of type number
, or undefined
.
When we want to iterate over the elements in the array and skip the undefined values, we would intuitively do it this way:
<script lang="ts">
export let numberArray: (number | undefined)[];
</script>
{#each numberArray.filter((element) => Boolean(element)) as item}
<!-- ❌ Argument of type 'number | undefined' is not assignable to parameter of type 'number'. ↓ -->
<p>{Math.floor(item)}</p>
{/each}
Given that Boolean(element)
, or element !== undefined
still does not protect item from not being undefined
, When we attempt to use the Math.floor() method, the compiler spits an error that we can’t use undefined values.
A common fix to this is to add a typescript assertion by adding item as number
to fix the compiler problem:
{#each numberArray.filter((element) => Boolean(element)) as item}
<p>{Math.floor(item as number)}</p> <!-- ❌ Unexpected token: "as" -->
{/each}
But because the markup doesn’t recognize as
as a valid token in the context of the block, the compiler again says: “No”.
How about using the !
non-null assertion?
{#each numberArray.filter((element) => Boolean(element)) as item}
<p>{Math.floor(item!)}</p> <!-- ❌ Unexpected token: "!" -->
{/each}
You get the picture… Svelte markup just can’t handle typescript syntax 😭.
So if you want to assert some type inside of your block when utilizing and {#each}
block, you’re out of luck.
The Workaround
You don’t get typescript syntax in your markup, but you do get it in your in your <script>
tags. So the best solution now is to pull the expression out of your {#each}
block, and pull it into a new variable where you can apply the type assertions.
In this example, we made this a reactive variable called filteredNumArray
.
<script lang="ts">
export let numberArray: (number | undefined)[]; // ✔️ Create new type asserted variable here ↓
$: filteredNumArray = numberArray.filter((element) => Boolean(element)) as number[];
</script>
{#each filteredNumArray as item}
<p>{Math.floor(item)}</p>
{/each}
The compiler is happy, you’re (kind of) happy, but most of all, the types work. You get to carry on with only one extra (kind of pointless) variable to manage.
Thankfully, the Svelte development team are working to allow typescript syntax inside markup with the upcoming release of Svelte 5. So if following that workaround above is not your style, you’re now able to create or upgrade current Svelte projects to the Svelte 5 beta.
If you’re not running a mission-critical app then I’d even recommend you do this, at least to get familiar with some of the new Svelte syntax moving forward.
Quirk 2: Non-updating Lists
If you have some exported property which is to be iterated over in your Svelte markup, but this value can change, then you may not see the changes reflected in your app.
For example:
<script lang="ts">
export let array: { name: string; age: number }[];
</script>
{#each array as item}
<p>{item.name}</p>
{/each}
Given that the value of the array is being passed down from a parent component, the {#each}
block will be printed when the component is mounted, however if the value changes in the parent - this won’t be reflected in the child. And when the array
value changes, you’re left showing the stale values of the old array.
The Fix
There’s a super simple solution to this, and it is to wrap the {#each}
block in a {#key}
block.
<script lang="ts">
export let array: { name: string; age: number }[];
</script>
{#key array}
{#each array as item}
<p>{item.name}</p>
{/each}
{/key}
The {#key}
block effectively destroys the containing element and recreates it every time one of its dependencies change.
The dependency (which is array
in our example) can be in the form of an expression or value, like this:
{#key dependency}
<!-- Contents -->
{/key}
Whenever dependency changes, the contents inside will be rerendered with the most up-to-date version of the application state.
Easter Eggs
If you’re looking for interesting ways to level up your workflow in Svelte, here are some less commonly known treats you can use inside your {#each}
blocks.
Trick #1: Local Constants - The {@const}
Tag
It took me a few months to start using this. I had seen it in passing and never took the 5 seconds it takes to understand it. Don’t be like me, and try to understand this.
The local constant {@const}
can exist inside an {#each}
block, which creates and assigns variable that exists only inside the scope of the {#each}
block. So now, anything can refer to the new local constant.
The {@const}
tag within {#each}
blocks are useful in 2 flavors:
- Improving readability
- Declaring some calculated value to apply to the values inside the block without having to do this for each instance.
For example, let’s improve the readability of this ugly {#each}
block I was working with today:
{#each Object.keys(improvements).filter((key) => !auditCats.includes(key)) as improvement, i}
<ProgressRadial
value={improvements[improvement].score}
meter={improvements[improvement].score > 0.89
? 'green'
: improvements[improvement].score > 0.49 && improvements[improvement].score < 0.9
? 'yellow'
: improvements[improvement].score < 0.5
? 'red'
: 'gray'}>
/>
{/each}
The amount of times we had to type improvements[improvement].score
in the {#each}
block is obnoxious and painful to read.
So let’s create the local variable score
:
{@const score = improvements[improvement].score}
And we can pop it at the top of the {#each}
block, and replace all instances of improvements[improvement].score
with just score
.
{#each Object.keys(improvements).filter((key) => !auditCats.includes(key)) as improvement, i}
{@const score = improvements[improvement].score}
<ProgressRadial
value={score}
meter={score > 0.89
? 'green'
: score > 0.49 && score < 0.9
? 'yellow'
: score < 0.5
? 'red'
: 'gray'}>
/>
{/each}
Beautiful, ain’t it!
This gets particularly useful if you wanted to do some processing to some of the nested values you wished to use inside as well
Let’s look at an example from the official Svelte Docs, with a slight amendment to better highlight the importance of the local constant:
{#each boxes as box}
{@const area = box.width * box.height}
<p>Area: {box.width} * {box.height} = {area}</p>
<p>Price in Outdoor Area: ${area * 3}</p>
<p>Price in Undercover Shed: ${area * 5}</p>
{/each}
Here we calculate the area
of each box, and make it local constant so that we can access it in the {#each}
block.
For comparison, without the {@const}
tag:
{#each boxes as box}
<p>Area: {box.width} * {box.height} = {box.width * box.height}</p>
<p>Price in Outdoor Area: ${box.width * box.height * 3}</p>
<p>Price in Undercover Shed: ${box.width * box.height * 5}</p>
{/each}
Don’t repeat the box.width * box.height
expression if you don’t have to!
Using Multiple Local Constants
You can use as many local constants as you’d like. This is important when iterating over fairly complex objects:
{#each data.meta.categories as category}
{@const creator = category.data.creator}
{@const details = category.data.details}
{@const date = category.data.date}
{@const formattedDay = `${date.day}, ${date.hour}:${date.minute}`} <img src={creator.imageUrl} />
<p>{creator.name}</p>
<p>{creator.email}</p>
<p>{details.title}</p>
<p>{details.description}</p>
<p>{formattedDay}</p>
{/each}
Here, have added local variables for the creator
, details
, and the category
.
Creating these, alleviates us from having to type a bunch of nested data inside the {#each}
block.
Without the {@const}
tags inside this {#each}
block, it would look like this:
{#each data.meta.categories as category}
<img src={category.data.creator.imageUrl} />
<p>{category.data.creator.name}</p>
<p>{category.data.creator.email}</p>
<p>{category.data.details.title}</p>
<p>{category.data.details.description}</p>
<p>{category.data.date.day}, {category.data.date.hour}:{category.data.date.minute}</p>
{/each}
For the sake of yourself and anyone who has to read your disgusting code, use {@const}
tags.
Trick #2: Destructuring Object Arrays Inside {#each}
Blocks
When iterating over an array of objects, you can destructure the iterated element in your {#each}
declaration.
For example, you wish to iterate over a block of users, where the each user is an object with the properties: name
, email
, and imageUrl
:
❌ Without destructuring:
{#each users as user}
<img src={user.imageUrl} />
<p>{user.name}</p>
<p>{user.email}</p>
{/each}
✔️ With destructuring:
{#each users as { imageUrl, name, email }}
<img src={imageUrl} />
<p>{name}</p>
<p>{email}</p>
{/each}
It’s just that little bit cleaner removing user.prop
each time you wish to access a property of the user
.
Conclusion
The {#each}
block in Svelte is relatively simple. Apart from its quirks mentioned in this article, it’s a nice feature to have.
It’s arguably more readable than something like .map
- which is what you find in most other Javascript frameworks.
It allows us to manage array element keys simply. And there’s just not much more to be said about it.
I hope {#each}
of you enjoyed this article, and are able to start incorporating {#each}
of these tips into your workflow. ✌️