Svelte 5 States: Avoiding Common Reactivity Traps
Aug 26, 2024
2 min read
Svelte 5 States: Avoiding Common Reactivity Traps
Svelte 5 introduces a powerful new reactivity system called “runes,” but with great power comes the potential for common pitfalls.
2 min. read
Svelte 5 introduces a new reactivity system with $state. While powerful, it comes with some potential pitfalls. Let's explore four common reactivity traps and how to avoid them.
1. The Tick Trap: Updating Form Inputs
When programmatically updating form input values and submitting the form, use await tick() to ensure the DOM has updated before submission.
html<script> let form; let name = $state(""); async function handleSubmit() { name = "New Name"; await tick(); // Wait for DOM update form.requestSubmit(); }</script><form bind:this={form}> <input bind:value={name} name="name" /> <button on:click={handleSubmit}>Submit</button></form>2. Deep Reactivity: Use Svelte-Specific Collections
For reactive updates with collections, Svelte 5 provides its own implementations of common data structures. Instead of using native JavaScript collections, use these Svelte-specific versions to ensure deep reactivity:
SvelteSet: A reactive version of JavaScript'sSetSvelteMap: A reactive version of JavaScript'sMapSvelteURL: A reactive version of theURLobjectSvelteDate: A reactive version of theDateobject
Example using SvelteSet
html<script> import { SvelteSet } from 'svelte/reactivity'; let items = $state(new SvelteSet(['apple', 'banana'])); function addItem(item) { items.add(item); // This will trigger reactivity } function removeItem(item) { items.delete(item); // This will also trigger reactivity }</script><ul> {#each [...items] as item} <li> {item} <button on:click={() => removeItem(item)}>Remove</button> </li> {/each}</ul><button on:click={() => addItem('cherry')}> Add Cherry</button>Using these Svelte-specific data structures ensures that any changes to the collections will trigger reactivity throughout your application, even for nested or complex data structures.
3. Preventing Circular Updates in Effects: The Untrack Solution
Use untrack() to prevent infinite update loops when modifying state inside an $effect.
html<script> import { untrack } from 'svelte'; let count = $state(0); $effect(() => { console.log(`Count is ${count}`); untrack(() => { if (count < 5) count++; // Won't trigger the effect again }); });</script><p>Count: {count}</p>4. Computed Values
For computed values, use $derived for simple calculations and $derived.by for more complex computations. This prevents circular updates and ensures efficient reactivity.
Simple Derived Values
For straightforward computations, use $derived:
html<script> let count = $state(0); let doubled = $derived(count * 2);</script><p>Count: {count}, Doubled: {doubled}</p><button on:click={() => count++}>Increment</button>Complex Derived Values
For more complex calculations, especially those involving loops or conditionals, use $derived.by:
html<script> let numbers = $state([1, 2, 3]); let total = $derived.by(() => { let sum = 0; for (const n of numbers) { sum += n; } return sum; });</script><button on:click={() => numbers.push(numbers.length + 1)}> {numbers.join(' + ')} = {total}</button>In this example, $derived.by efficiently computes the sum of the numbers array, updating only when the array changes.
Conclusion
By being aware of these traps and using tick(), Svelte-specific data structures, untrack(), and $derived/$derived.by, you can effectively use Svelte 5's reactivity system while avoiding common pitfalls.
For more details, check out the official Svelte 5 preview documentation:
https://svelte-5-preview.vercel.app/docs/introduction
