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'sSet
SvelteMap
: A reactive version of JavaScript'sMap
SvelteURL
: A reactive version of theURL
objectSvelteDate
: A reactive version of theDate
object
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.