Effects connect Svelte state to the outside world: canvases, timers, third-party libraries, localStorage.
`$effect(fn)` runs fn after the component is mounted and again whenever state it read changes, after the DOM has been updated. It is for side effects: drawing on a canvas, syncing to localStorage, setting up a library, logging analytics.
$effect(() => {
const id = setInterval(() => elapsed++, interval);
return () => clearInterval(id); // teardown
});- Dependencies are tracked automatically: any
$state,$derivedor prop read synchronously inside the function. Values read after anawaitor inside asetTimeoutcallback are not tracked. - If the function returns a teardown function, it runs before the effect re-runs and when the component is destroyed.
- Effects run only in the browser, never during server-side rendering.
- Use
untrack(() => value)to read something without depending on it. $effect.preruns before the DOM update (for example to preserve scroll position).- Svelte batches changes, so the effect runs once even if several dependencies changed.
When not to use an effect: to compute a value from state (use $derived), or to respond to a user action (do it in the event handler). Writing state inside an effect that also reads it can create loops; Svelte throws effect_update_depth_exceeded when an effect keeps re-triggering itself.
For one-time setup on mount, an effect that reads no state runs once; onMount from svelte also still works.
Example
<script>
let interval = $state(1000);
let elapsed = $state(0);
let canvas;
$effect(() => {
const id = setInterval(() => elapsed++, interval); // depends on interval
return () => clearInterval(id); // cleanup before re-run
});
$effect(() => {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 200, 20);
ctx.fillRect(0, 0, (elapsed % 10) * 20, 20); // redraw when elapsed changes
});
$effect(() => {
localStorage.setItem('interval', String(interval)); // sync to storage
});
</script>
<canvas bind:this={canvas} width="200" height="20"></canvas>
<p>{elapsed} ticks</p>
<button onclick={() => (interval /= 2)}>faster</button>Remember
$effectruns after mount and after DOM updates when its tracked state changes- Return a teardown function to clean up timers and listeners
- Use
$derivedfor values and event handlers for user actions; effects are an escape hatch
Watch out for
- Reading state after an
awaitinside an effect and expecting it to be a dependency.