sveltejs / sveltejs/kit

Chaining a $derived that depends on an awaited remote function in the script tag may cause it to rerun (practically) infinitely

Open
#16,119 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

svelte 5
Dominant language
JavaScript
Stars
20.8k
Forks
2.3k
Avg merge
1d 16h
Merged PRs (30d)
156

Description

Describe the bug

Say you have something like:

<script lang="ts">
import { getData, subData } from '$lib/rem/data.remote';

	const mappedData = $derived(mapData(await getData({ dummyFilters: '' })));

	function mapData(d: { test: string }[]) {
		console.log('mapping ran');
		return d.map((item) => ({ ...item, mapped: true }));
	}
</script>

<form {...subData}>

	<input {...subData.fields.fData.as('text')}/>
	<button type="submit">Submit</button>
</form>
<ul>
	{#each mappedData as item}
		<li>{item.test} - {item.mapped}</li>
	{/each}
</ul>

Straightforward scenario, just a remote query that fetches some dummy data, then the page displays it in a list. The data is mapped before it is rendered and awaited directly in the script tag (although in my project it was in a @const inside a boundary, the basics of this are still the same).

Clicking on the button submits the form. Once the form finishes, the data is updated. In my repro the form does nothing as it's irrelevant to the issue. If you, however, look in the console, you'll notice that "mapping ran" is printed two times. Kind of weird, I'd expect it to run only once, but I just chalked it up to "Svelte magic" and didn't question it. (In my real project, it actually ran like 5 or 6 times, I guess it depends on the rows it needs to render?)

Now here's the issue. Literally the same code, except we extract the remote function call into a separate variable because we're responsible software engineers and are trying to keep the code nice to look at:

<script lang="ts">
	import { getData, subData } from '$lib/rem/data.remote';

	const data = $derived(await getData({ dummyFilters: '' }));
	const mappedData = $derived(mapData(data));

//everything else identical
</script>

Click the button again, now it runs 5-6 times. Why is this a problem? Because in my real app (where I have a lot of rendered components inside a table, with popovers over some columns and rows, and the returned data is itself part of other deriveds in the table component, etc) it's more like 60.000 re-runs of the mapping function, effectively freezing the page for a good 10 seconds.

But wait, there's more! Let's add a $inspect('data, data). The data only appears once, inspecting it shows it isn't actually being marked as stale to warrant all those function calls.

Ok, whatever, maybe it's a quirk of $inspect. But let's make this even weirder:


	$inspect('data', data)
	$inspect('mappedData', mappedData)

Add inspects over both the props, click the button and... no more error. Console log runs only once, as it's supposed to. Except it's still buggy because, even though in the repro console log nothing happens, in my real app i get a whole bunch of that "derived is disconnected, stale data" warning in the console, so clearly the underlying issue is somewhere even deeper.

Reproduction

https://github.com/big-mike-88/async-derived-error

Click the button, watch the console logs.

Logs

System Info
System:
    OS: macOS 26.5
    CPU: (11) arm64 Apple M3 Pro
    Memory: 124.83 MB / 18.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 26.0.0 - /opt/homebrew/bin/node
    npm: 11.12.1 - /opt/homebrew/bin/npm
    bun: 1.3.14 - /opt/homebrew/bin/bun
    Deno: 2.8.1 - /opt/homebrew/bin/deno
  Browsers:
    Safari: 26.5
  npmPackages:
    @sveltejs/adapter-node: ^5.5.5 => 5.5.5 
    @sveltejs/kit: next => 2.66.0 
    @sveltejs/vite-plugin-svelte: ^7.1.2 => 7.1.2 
    svelte: ^5.56.1 => 5.56.3 
    vite: ^8.0.16 => 8.0.16
Severity

serious, but I can work around it

Additional Information

Possibly related to https://github.com/sveltejs/svelte/issues/18355 . It is very similar to what I'm experiencing, even down to the fact that if i remove the {#each} loop the function fires twice instead of 6 times (which honestly is still kind of weird). Although I don't do any async work inside the loop itself, nor do i launch any batches. So maybe this is a separate thing.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked reproduction repository and the Svelte component using the script-tag $derived values, then reproduce the button submission while watching the mapping logs. Trace the interaction between awaited remote functions, separated $derived values, and rendered each blocks; done means the mapping does not rerun excessively after submission and a regression test covers the behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
frontend, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.