iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

$effect

The \$effect rune runs side effects after the DOM updates — subscriptions, manual DOM work, syncing to external stores. Cleanups run on dependency change and on component teardown.

Effect, cleanup, dependency tracking

EXAMPLE
<script>
    let count    = $state(0);
    let url      = $state('/api/posts');
    let online   = $state(navigator.onLine);

    // 1) Basic — runs on mount and any time count changes
    $effect(() => {
        document.title = `Count: ${count}`;
    });

    // 2) Cleanup — return a function
    $effect(() => {
        const id = setInterval(() => count++, 1000);
        return () => clearInterval(id);
    });

    // 3) Listen to window events
    $effect(() => {
        const on = () => online = navigator.onLine;
        window.addEventListener('online',  on);
        window.addEventListener('offline', on);
        return () => {
            window.removeEventListener('online',  on);
            window.removeEventListener('offline', on);
        };
    });

    // 4) Async work — race-safe with an `active` flag
    let posts = $state([]);
    $effect(() => {
        let active = true;
        (async () => {
            const res = await fetch(url);
            const data = await res.json();
            if (active) posts = data;
        })();
        return () => { active = false; };
    });

    // 5) $effect.pre — runs BEFORE DOM updates (instead of after)
    $effect.pre(() => {
        if (window.scrollY > 100) {
            // measure something before the new render commits
        }
    });

    // 6) Tracked vs untracked — only reads inside the effect become dependencies
    let a = $state(0), b = $state(0);
    $effect(() => {
        console.log('runs when a changes:', a);
        // reading b synchronously here would make it a dependency too
    });

    // To explicitly NOT track a read, use untrack from svelte
    import { untrack } from 'svelte';
    $effect(() => {
        console.log(`a=${a}, b=${untrack(() => b)}`);
        // runs only when a changes; b changes silently
    });

    // 7) $effect.root — start an effect outside a component
    // Useful for global subscriptions in .svelte.js modules
    // const stop = $effect.root(() => {
    //     $effect(() => { ... });
    //     return () => { /* cleanup */ };
    // });
    // stop();   // tear down everything when done

    // 8) Don't mutate state INSIDE an effect — leads to infinite loops
    // BAD:
    // $effect(() => { count = count + 1; });   // infinite

    // 9) Don't use $effect for derived values — use $derived
    // BAD: let double = $state(0); $effect(() => { double = count * 2; });
    // GOOD: let double = $derived(count * 2);

    // 10) Common patterns

    // a) Sync to localStorage
    let theme = $state(localStorage.getItem('theme') ?? 'light');
    $effect(() => {
        localStorage.setItem('theme', theme);
        document.documentElement.setAttribute('data-theme', theme);
    });

    // b) Subscribe to an external store
    $effect(() => {
        const unsub = userStore.subscribe(u => /* … */ {});
        return unsub;
    });

    // c) Auto-focus when a condition becomes true
    let inputEl;
    let isEditing = $state(false);
    $effect(() => {
        if (isEditing) inputEl?.focus();
    });
</script>

<input bind:this={inputEl} />
<button onclick={() => isEditing = !isEditing}>Toggle</button>
<p>Online: {online}</p>

Why it matters

\$effect is for syncing to the world (DOM, network, storage). For derived values, use \$derived — effects can’t replace it, and writing state inside an effect is the most common new-Svelte mistake.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
$effect(() => {
    console.log('count is', count);
    return () => { /* cleanup */ };
});
Try it Yourself »

Exercise

Run a side effect when deps change.

(() => console.log(count));

Discussion

Loading…