03 - Effect ($effect)
What is $effect?
$effect() runs side effects when dependencies change. Side effects are things like logging, fetching data, setting up timers, or interacting with the DOM — anything that isn't a pure computation.
Svelte 4 (Legacy)
<script>
let count = 0;
$: console.log(`Count changed to: ${count}`); // $: was used for both derived values AND side effects
</script>Svelte 5
<script>
let count = $state(0);
$effect(() => {
console.log(`Count changed to: ${count}`); // $effect is explicitly for side effects
});
</script>Svelte 5 separates concerns: $derived for computed values, $effect for side effects. In Svelte 4, $: handled both, making intent unclear.
Basic Usage
Dependencies are tracked automatically — any $state referenced inside the effect triggers it to re-run.
<script>
let count = $state(0);
$effect(() => {
console.log(`Count changed to: ${count}`); // re-runs whenever count changes
});
</script>Cleanup
Return a function from $effect to clean up resources (timers, event listeners, subscriptions). The cleanup runs before the effect re-runs and when the component is destroyed.
<script>
let seconds = $state(0);
$effect(() => {
const interval = setInterval(() => {
seconds++;
}, 1000);
return () => clearInterval(interval); // cleanup: runs before re-run and on destroy
});
</script>$effect.pre() — Before DOM Updates
$effect() runs after the DOM updates. Use $effect.pre() when you need to run code before the DOM is updated (e.g., to measure or prepare something).
<script>
let value = $state('');
$effect.pre(() => {
console.log('Before DOM update:', value);
});
</script>$effect.root() — Manual Lifecycle Control
Normally, effects are tied to the component lifecycle — they auto-cleanup when the component unmounts. $effect.root() lets you control when effects start and stop manually.
Use cases: creating effects outside components, building libraries, or dynamically managing reactive systems.
<script>
const cleanup = $effect.root(() => {
$effect(() => {
console.log('This effect can be cleaned up manually');
});
return () => {
console.log('All effects inside this root are cleaned up');
};
});
// Call cleanup() whenever you want to stop all effects inside the root
cleanup();
</script>Key Points
- Replaces Svelte 4's
$:for side effects (not computed values — use$derivedfor those) - Dependencies are tracked automatically
- Return a function for cleanup (timers, listeners, subscriptions)
- Runs after DOM updates by default; use
$effect.pre()for before - Avoid overusing — if you're computing a value, use
$derivedinstead
Interactive Example
<script>
let count = $state(0);
let logs = $state([]);
// Basic effect — re-runs when count changes
$effect(() => {
logs.push(`Count changed to: ${count}`);
if (logs.length > 5) logs.shift();
});
// Timer with cleanup — cleanup runs when timerRunning changes or component destroys
let seconds = $state(0);
let timerRunning = $state(false);
$effect(() => {
if (!timerRunning) return;
const interval = setInterval(() => {
seconds++;
}, 1000);
return () => clearInterval(interval);
});
// Effect that runs before DOM updates
let inputValue = $state('');
let preUpdateLog = $state([]);
$effect.pre(() => {
preUpdateLog.push(`Before DOM: ${inputValue}`);
if (preUpdateLog.length > 3) preUpdateLog.shift();
});
</script>
<div>
<h2>03 - Effect Example</h2>
<div>
<h3>Basic Effect (logs changes)</h3>
<button onclick={() => count++}>Count: {count}</button>
<div>
<strong>Effect logs:</strong>
{#each logs as log}
<div>{log}</div>
{/each}
</div>
</div>
<div>
<h3>Effect with Cleanup (timer)</h3>
<p>Seconds: {seconds}</p>
<button onclick={() => timerRunning = !timerRunning}>
{timerRunning ? 'Stop' : 'Start'} Timer
</button>
<button onclick={() => seconds = 0}>Reset</button>
</div>
<div>
<h3>$effect.pre() — Before DOM Update</h3>
<input bind:value={inputValue} placeholder="Type something" />
<div>
<strong>Pre-update logs:</strong>
{#each preUpdateLog as log}
<div>{log}</div>
{/each}
</div>
</div>
</div>
<style>
div { margin: 20px; }
h3 { margin-top: 20px; }
button, input { margin: 5px; }
</style>