Skip to content

Instantly share code, notes, and snippets.

@the0neWhoKnocks
Last active August 28, 2025 20:25
Show Gist options
  • Save the0neWhoKnocks/626b9af65628071672781135b0b685b7 to your computer and use it in GitHub Desktop.
Save the0neWhoKnocks/626b9af65628071672781135b0b685b7 to your computer and use it in GitHub Desktop.

Migration From Older to v5

A full list of changes can be found at https://svelte.dev/docs/svelte/v5-migration-guide. This is a list of the most common things I had to deal with.

Props

<script>
  // OLD
  export let var1 = true;
</script>
<script>
  // NEW
  let { var1 = true } = $props();
</script>

Reactive Component Variables

$state

<script>
  // OLD
  let var1 = { fu: 'bar' };
</script>
<script>
  // NEW
  let var1 = $state({ fu: 'bar' });
</script>

When you don't need a deeply reactive Object/Array, or if you're doing a === comparison, use $state.raw.

<script>
  // OLD
  let var1 = true;
</script>
<script>
  // NEW
  let var1 = $state.raw(true);
</script>

$derived

<script>
  // OLD
  let var1 = 1;
  let var2 = var1 + 4;
</script>
<script>
  // NEW
  let var1 = $state.raw(1);
  let var2 = $derived(var1 + 4);
</script>

Reactive declarations

  • $derived for setting one variable value.

    <script>
      // OLD
      $: if ($enabled) {
        var2 = true;
      }
    </script>
    <script>
      // NEW
      let var2 = $derived($enabled);
    </script>
    <script>
      // OLD
      $: if ($enabled) {
        // a lot of complicated logic that eventually sets `var2`
      }
    </script>
    <script>
      // NEW
      let var2 = $derived.by(() => {
        return // a lot of complicated logic that eventually sets `var2`
      });
    </script>
  • $effect for mutating multiple items.

    <script>
      // OLD
      $: if ($enabled) {
        var2 = true;
        var3 = 55;
      }
    </script>
    <script>
      // NEW
      $effect(() => {
        var2 = true;
        var3 = 55;
      });
    </script>

Binding an input value:

<script>
  // OLD
  export let checked = false;
</script>
<input bind:{checked} />
<script>
  // NEW
  let { checked = $bindable(false) } = $props();
</script>
<input bind:{checked} />

Slots (Snippets)

To avoid conflicts of snippet functions and prop names, I prefix the snippet name with s_.

Named Slots

  • Implementing a named slot:
    <!-- OLD -->
    <slot name="opt" {label} {value}>default value</slot>
    <!-- NEW -->
    {#if s_opt}{@render s_opt({ label, value })}{:else}default value{/if}
  • Using a named slot:
    <!-- OLD -->
    <Comp let:label let:value>
      <option slot="opt" {value}>{label}</option>
    </Comp>
    <!-- NEW -->
    <Comp>
      {#snippet s_opt({ label, value })}
        <option {value}>{label}</option>
      {/snippet}
    </Comp>
  • {#snippet children(var)} children snippet only has to be defined when parameters are passed to the snippet, otherwise you don't need it for children of a Component.

Events

  • Replace all on:<Event> props with on<Event>. So on:click becomes onclick.
  • Any component waiting for a custom component event that used to use createEventDispatcher should alter it's handler argument and ditch the detail prop.

DOM Attributes

Custom attributes are not applied or stripped away if they're not standard for a DOM element. Only way to get around this is by manually setting the attribute or changing the attribute to something like data-<Attribute>.

<!-- OLD -->
<div class="folder" {open}>
<!-- NEW (switch to data attribute) -->
<div class="folder" data-open={open || null}>
<script>
  // NEW (manually set attribute)
  let folderRef;
  $effect(() => {
    (open)
      ? folderRef.setAttribute('open', '')
      : folderRef.removeAttribute('open');
  });
</script>

<div class="folder" bind:this={folderRef}>

Debugging

  • Using console.log for $state values seems to cause an infinite loop sometimes. Instead use $inspect(<state_var>);, usually just place it after the var definition. $inspect will track all updates so it doesn't have to be placed in specific areas.

Troubleshooting

  • Component re-rendering everything on one property change (often happens when rendering multple items via each).

    • Passing an entire Object for an #each key can cause full re-renders of elements. So try to set a unique key that isn't a prop that gets updated (unless you want it to trigger an update).
      <!-- could cause issues -->
      {#each arr of item (item)}
      <!-- could cause issues -->
      {#each arr of item (item.name)}
  • Whitespace not trimmed consistently.

    {#if epName}- {#if epNumCombined}({epNumCombined}) {/if}{epName}{/if}

    Old: - (25) Some Name.
    Current: - (25)Some Name.

    To get around this, you can force a space character ({' '}):

    {#if epName}- {#if epNumCombined}({epNumCombined}){' '}{/if}{epName}{/if}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment