Svelte 5 States: Avoiding Common Reactivity Traps
Aug 26, 2024
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
Here's an 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.
