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 $derived for 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 $derived instead

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>