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

onMount / onDestroy

onMount is the “run when the component is in the DOM” hook. Pair with cleanup for subscriptions; for async fetch, prefer top-level await + Suspense, or the SvelteKit load function.

onMount, onDestroy, async, alternatives

EXAMPLE
<script>
    import { onMount, onDestroy, tick, beforeUpdate, afterUpdate, untrack } from 'svelte';

    let inputEl;
    let count = $state(0);
    let online = $state(navigator.onLine);
    let mounted = $state(false);

    // 1) Focus an input on mount
    onMount(() => {
        inputEl.focus();
        mounted = true;
    });

    // 2) Subscribe + clean up
    onMount(() => {
        const onChange = () => online = navigator.onLine;
        window.addEventListener('online',  onChange);
        window.addEventListener('offline', onChange);
        return () => {
            window.removeEventListener('online',  onChange);
            window.removeEventListener('offline', onChange);
        };
    });

    // 3) Interval / animation
    onMount(() => {
        const id = setInterval(() => count++, 1000);
        return () => clearInterval(id);
    });

    // 4) Async work inside onMount (return for cleanup; use a flag for cancellation)
    let data = $state(null);
    onMount(() => {
        let active = true;
        (async () => {
            const r = await fetch('/api/data');
            const json = await r.json();
            if (active) data = json;
        })();
        return () => { active = false; };
    });

    // 5) onDestroy — cleanup when the component is removed
    onDestroy(() => {
        // remove DOM listeners, cancel timers, dispose resources
        console.log('component destroyed');
    });

    // 6) beforeUpdate / afterUpdate — DOM measurement timing
    beforeUpdate(() => {
        // about to re-render; read pre-update DOM state if needed
    });
    afterUpdate(() => {
        // DOM is updated; safe to measure / scroll-to-bottom
    });

    // 7) tick() — wait until the next DOM flush
    async function addAndScroll() {
        messages.push(newMessage);
        await tick();              // DOM has now updated
        container.scrollTop = container.scrollHeight;
    }

    // 8) Compare to runes — $effect runs every time deps change
    $effect(() => {
        if (count > 5) {
            console.log('count exceeded 5');
        }
    });
    // $effect cleanup runs on dep change AND on unmount

    // 9) Common bugs
    //   • Accessing the DOM in script top-level (before onMount) → element refs are null
    //   • Forgetting cleanup → leaks on remount (HMR, navigation)
    //   • Setting state inside onDestroy → unnecessary work; just clean resources
    //   • Heavy work in onMount → blocks first paint; defer with await tick() or setTimeout 0
</script>

<!-- 10) DOM ref pattern -->
<input bind:this={inputEl} placeholder="Search…" />

<!-- 11) Lifecycle order (Svelte 5) -->
<!--
     script setup → $state, $derived initialised
     onMount    — after first DOM render
     beforeUpdate / afterUpdate — around state changes
     $effect    — after relevant state changes
     onDestroy  — symmetric with onMount
-->

<!-- 12) SvelteKit alternative — load function (runs on server + client) -->
<!-- +page.js -->
<script context="module">
export async function load({ fetch, params }) {
    const res = await fetch(`/api/posts/${params.id}`);
    return { post: await res.json() };
}
</script>

<!-- Or +page.server.js for server-only -->
<!-- +page.server.js -->
<!--
import { db } from $lib/db';
export async function load({ params }) {
    return { post: await db.posts.findById(params.id) };
}
-->

<!-- 13) Async setup with Suspense (experimental in Svelte 5) -->
<!-- {#await fetchData()} -->
<!--     <Spinner /> -->
<!-- {:then data} -->
<!--     <UserCard {data} /> -->
<!-- {:catch error} -->
<!--     <p>Error: {error.message}</p> -->
<!-- {/await} -->

<!-- 14) Real patterns -->

<!-- a) IntersectionObserver lazy-load images -->
<script>
    import { onMount, onDestroy } from 'svelte';
    let imgEl;
    let loaded = $state(false);
    let observer;

    onMount(() => {
        observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting) {
                loaded = true;
                observer.disconnect();
            }
        });
        observer.observe(imgEl);
    });

    onDestroy(() => observer?.disconnect());
</script>

<img
    bind:this={imgEl}
    src={loaded ? '/photo.jpg' : '/placeholder.svg'}
    loading="lazy"
/>

<!-- b) Drag handler with cleanup -->
<script>
    onMount(() => {
        const onDown = (e) => start(e);
        const onMove = (e) => move(e);
        const onUp   = (e) => end(e);
        document.addEventListener('mousedown', onDown);
        document.addEventListener('mousemove', onMove);
        document.addEventListener('mouseup',   onUp);
        return () => {
            document.removeEventListener('mousedown', onDown);
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup',   onUp);
        };
    });
</script>

<!-- c) Initial fetch on mount -->
<script>
    let users = $state([]);
    let loading = $state(true);
    let error = $state(null);

    onMount(async () => {
        try {
            const r = await fetch('/api/users');
            users = await r.json();
        } catch (e) {
            error = e.message;
        } finally {
            loading = false;
        }
    });
</script>

{#if loading}
    <Spinner />
{:else if error}
    <p class="error">{error}</p>
{:else}
    <ul>
        {#each users as user (user.id)}
            <li>{user.name}</li>
        {/each}
    </ul>
{/if}

<!-- 15) Best practices -->
<!-- - For SSR-friendly data fetching, prefer SvelteKit load() over onMount -->
<!-- - Return a cleanup function from onMount when you subscribe / time / observe -->
<!-- - Read DOM in onMount, not in script setup -->
<!-- - For reactive side effects driven by state, use $effect instead of onMount/onDestroy -->
<!-- - Don't fetch data in onMount that the server could have rendered -->

Why it matters

For data, prefer SvelteKit’s load() over onMount — you get SSR, prefetch, and progressive enhancement for free. onMount is for DOM-touching work; \$effect handles reactive side effects more cleanly.

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

Example

Example
import { onMount, onDestroy } from 'svelte';
onMount(() => console.log('mounted'));
onDestroy(() => console.log('bye'));
Try it Yourself »

Discussion

Loading…