Svelte and SvelteKit course · Module 7: Lifecycle

Valhalla - Component Lifecycle

2 min read ·
In this lesson4

Welcome to Valhalla! Just as warriors arrive and depart from these halls, components have their own lifecycle - from creation to destruction.

Lifecycle in Svelte

Component created
       ↓
   onMount()     ← Mounted in DOM
       ↓
   [life]        ← $effect.pre / $effect when state changes
       ↓
  onDestroy()    ← Removed from DOM

Svelte 5 has no hooks that run on every update of the whole component. Instead, effects ($effect.pre, $effect) react only to the state they use - you will get to know them better in the next lesson.

onMount - Arrival at Valhalla

<script>
  import { onMount } from 'svelte';

  let data = $state(null);

  onMount(() => {
    console.log('Component mounted!');

    // Fetch data
    fetch('/api/warriors')
      .then(r => r.json())
      .then(d => data = d);

    // Cleanup (optional)
    return () => {
      console.log('Component will be unmounted');
    };
  });
</script>

onDestroy - Departure for Ragnarok

<script>
  import { onDestroy } from 'svelte';

  const interval = setInterval(() => {
    console.log('tick');
  }, 1000);

  onDestroy(() => {
    clearInterval(interval);
    console.log('Cleaned up!');
  });
</script>

Svelte 5: $effect with a Cleanup Function

$effect runs after the component is mounted, and the function it returns cleans up on unmount. An effect that reads no state runs once - like onMount and onDestroy in one:

<script>
  // The effect reads no state, so it runs once after mounting
  $effect(() => {
    console.log('Mounted!');

    const interval = setInterval(() => {
      console.log('tick');
    }, 1000);

    return () => {
      clearInterval(interval);
      console.log('Unmounted!');
    };
  });
</script>

When an effect reads state (for example count), it runs again after every change, and before each new run it calls its cleanup function. For code that must run exactly once, onMount is still a good choice.

Life in Valhalla has its rhythm - just like the lifecycle of components!

Code for this lesson: App.svelte
<script>
  // Valhalla - Component lifecycle
  // onMount, onDestroy and $effect with a cleanup function
  import { onMount, untrack } from 'svelte';
  import Einherjar from './Einherjar.svelte';

  let mounted = $state(false);
  let showWarrior = $state(true);
  let logs = $state([]);

  // untrack: log can be called even from an effect, and writing to logs
  // does not become its dependency (more in lesson 7_2)
  function log(message) {
    untrack(() => {
      logs = [...logs, { time: new Date().toLocaleTimeString(), message }].slice(-8);
    });
  }

  onMount(() => {
    mounted = true;
    log('onMount (App) - Valhalla opens its gates');
  });
</script>

<main class="lifecycle">
  <h1>Valhalla - Lifecycle</h1>
  <p class="subtitle">onMount, onDestroy, $effect</p>

  <section>
    <h2>Status</h2>
    <p>App mounted: <strong>{mounted ? 'Yes' : 'No'}</strong></p>
    <button onclick={() => (showWarrior = !showWarrior)}>
      {showWarrior ? 'Send the warrior to Ragnarök' : 'Summon the warrior'}
    </button>
  </section>

  {#if showWarrior}
    <Einherjar onLog={log} />
  {:else}
    <p class="empty">The warrior has left. Summon him to see onMount again.</p>
  {/if}

  <section class="logs">
    <h2>Lifecycle logs</h2>
    {#each logs as entry}
      <p><span class="time">{entry.time}</span> {entry.message}</p>
    {/each}
  </section>
</main>

<style>
  .lifecycle {
    font-family: 'Georgia', serif;
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem;
    background: linear-gradient(135deg, #1a0a0a 0%, #2a1a0a 100%);
    color: #e8d5b7;
    min-height: 100vh;
  }

  h1 {
    color: #ffd700;
    text-align: center;
  }

  .subtitle {
    text-align: center;
    color: #8b7355;
  }

  section {
    background: rgba(212, 175, 55, 0.1);
    border: 1px solid #d4af37;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  h2 {
    color: #d4af37;
    font-size: 1rem;
    margin-top: 0;
  }

  strong {
    color: #ffd700;
  }

  button {
    padding: 0.5rem 1rem;
    background: #3d2914;
    color: #e8d5b7;
    border: 1px solid #d4af37;
    border-radius: 4px;
    cursor: pointer;
  }

  .empty {
    color: #8b7355;
    font-style: italic;
  }

  .logs {
    max-height: 250px;
    overflow-y: auto;
  }

  .logs p {
    margin: 0.25rem 0;
    padding: 0.25rem 0.5rem;
    background: rgba(0,0,0,0.3);
    border-radius: 4px;
    font-size: 0.9rem;
  }

  .time {
    color: #8b7355;
    font-family: monospace;
    margin-right: 0.5rem;
  }
</style>

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. When is the onMount function called?

  2. 2. How do you perform cleanup in onMount?

Useful articles