Learn from common mistakes during Svelte 5 migration.
1. Forgetting $state
svelte<!-- ā Not reactive in runes mode --> <script> let count = 0; // Forgot $state! $effect(() => console.log(count)); // Triggers $effect usage = runes mode </script> <!-- ā Correct --> <script> let count = $state(0); $effect(() => console.log(count)); </script>
2. Assigning to $derived
svelte<script> let count = $state(0); let doubled = $derived(count * 2); // ā Error! Can't assign to derived function reset() { doubled = 0; } // ā Change the source function reset() { count = 0; } </script>
3. Missing Optional Chaining for Events
svelte<script> let { onsubmit } = $props(); </script> <!-- ā Crashes if onsubmit not provided --> <button onclick={() => onsubmit()}>Submit</button> <!-- ā Safe with optional chaining --> <button onclick={() => onsubmit?.()}>Submit</button> <!-- ā Or provide default --> <script> let { onsubmit = () => {} } = $props(); </script>
4. Forgetting to Render Children
svelte<script> let { children } = $props(); </script> <!-- ā Children is a snippet, not a value --> <div>{children}</div> <!-- ā Must render it --> <div>{@render children()}</div>
5. Array Mutation Without Reassignment
svelte<script> let items = $state(['a', 'b']); </script> <!-- ā May not update (depends on context) --> <button onclick={() => items.push('c')}>Add</button> <!-- ā Reassignment always works --> <button onclick={() => items = [...items, 'c']}>Add</button>
6. Effect Without Cleanup
svelte<script> // ā Memory leak - interval never cleared $effect(() => { setInterval(() => count++, 1000); }); // ā Return cleanup function $effect(() => { const i = setInterval(() => count++, 1000); return () => clearInterval(i); }); </script>
š Migration pitfalls