Skip to content

Using a .refresh() breaks onMount so that it no longer works on components under an {#each} #14846

@mihaipoenaru

Description

@mihaipoenaru

Describe the bug

This is a weird one. Let's say you have two remote functions, one that gets a counter, one that increments it.

import { query, command } from '$app/server';

let counter = 0;

export const incCounter = command(async () => {
	counter++;
  //getCounter().refresh() (no refresh just yet, uncomment in the next step)
});

export const getCounter = query(async () => {
	return counter;
});

Now let's say we have a button that increments a counter, and beneath it we have the "event history" component, which should show the history of the values.

<script lang="ts">
import {getCounter,incCounter} from './repro.remote'
    import Event from '$lib/Event.svelte'

    const counterHist:number[] = $state([])
    const c = $derived(await getCounter())
</script>
 
 {c}
 <button onclick={async () => {
    await incCounter()
    counterHist.push(c + 1)
 }}>counter++</button>

<ul>
{#each counterHist as h}
    <li>
        <Event c={h}/>
    </li>
{/each}
</ul>

The Event component just shows some text, and the value of the counter that was provided to it. It also shows a boolean that is set to true in its onMount, so it should only print true (foreshadowing).

<script lang="ts">
import {onMount} from 'svelte'

let mounted = $state(false)
const {c} = $props()

onMount(() => {
  mounted = true
})
</script>

Inc counter from {c} ------> {mounted}

Now the refresh in incCounter is commented, so let's click the button a bunch of times:

Inc counter from 1 ------> true
Inc counter from 1 ------> true

So far, so basic. The counter shown remains at 1 (obvious, since we don't call refresh yet) and the boolean is true (obvious because how could it not be?)

Now let's uncomment the refresh for the getCounter in the incCounter function, refresh the page, and click the button a bunch more times

Inc counter from 1 ------> false
Inc counter from 2 ------> false
Inc counter from 3 ------> false
Inc counter from 4 ------> false
Inc counter from 5 ------> false

Ok, good, so the counter is incrementing but... what's this? The onMount no longer runs. And it only breaks with an {#each} block; I've tried with an {#if} and a boolean instead of a list, but it works. The refresh on remote functions just seem to break each blocks...

Reproduction

https://stackblitz.com/edit/sveltejs-kit-template-default-bumnmkhj?file=src%2Froutes%2Frepro.remote.ts

  1. Click the button a bunch of times
  2. Uncomment the refresh and then refresh the page
  3. Click the button again -> onMount no longer runs

Logs

N/A

System Info

Svelte is not the latest version because of another bug ( #14843 ), but the stackblitz is on the latest svelte

  System:
    OS: macOS 26.0.1
    CPU: (11) arm64 Apple M3 Pro
    Memory: 123.41 MB / 18.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.10.0 - /opt/homebrew/bin/node
    npm: 11.6.0 - /opt/homebrew/bin/npm
    Deno: 2.5.4 - /opt/homebrew/bin/deno
  Browsers:
    Safari: 26.0.1
  npmPackages:
    @sveltejs/adapter-node: ^5.3.2 => 5.4.0 
    @sveltejs/kit: ^2.43.4 => 2.48.3 
    @sveltejs/vite-plugin-svelte: ^6.2.1 => 6.2.1 
    svelte: 5.42.2 => 5.42.2 
    vite: ^7.0.0 => 7.1.12

Severity

blocking an upgrade

Additional Information

Not sure how to rank the annoyance. The bug messes up some pop-ups that auto-expire after 5 seconds, but the setTimeout to expire them is set in onMount, so they never disappear. The user can just click X to get them to go away, so in this case it's not the end of the world, but it may be a much more serious issue down the line...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions