Repository navigation
form remote functions with reactive initial values #15500
Description
Describe the bug
Suppose I have a form which has initial values dependent on some sort of reactive data. It can be $state, $derived, etc. I need to have that data in the initial value of some input fields. I want all the fields to be loaded during SSR so that users don't see unloaded fields before hydration, so $effect seems to be out of the question.
This is what I initially tried:
<script lang="ts">
import { page } from "$app/state";
import { resolve } from "$app/paths";
import { loadId, sendId } from "../../data.remote";
const idPrefix = $derived(page.params.idPrefix ?? "default");
const id = $derived(await loadId({ idPrefix }));
const form = $derived(sendId.for(id.received));
form.fields.set({ text: `Default text for ${id.received}` });
</script>
<form {...form}>
<input
type="text"
placeholder="Enter a value to send to the server"
{...form.fields.text.as("text")}
/>
<button type="submit">Submit</button>
</form>This doesn't work, as I'm referencing form and id outside of a closure. If I were to do a client navigation to the same page but different params, I'd get stale input fields.
I then tried to put the form.fields.set call in form's $derived:
const form = $derived.by(() => {
const form = sendId.for(id.received);
form.fields.set({ text: `Default text for ${id.received}` });
return form;
});This doesn't work either, as form.fields.set is itself a state mutation, triggering this error when e.g. submitting the form:
Uncaught Svelte error: state_unsafe_mutation
Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
https://svelte.dev/e/state_unsafe_mutation
Currently I'm using a workaround like so:
const form = $derived.by(() => {
const text = `Default text for ${id.received}`;
const form = sendId.for(id.received);
untrack(() => form.fields.set({ text }));
return form;
});This works, but all the initial values have to be calculated outside of untrack, which isn't quite neat.
I supposed that the form remote function might be lacking some sort of initial value mechanism, which is why I filed an issue. What I have in mind would be a bit similar to .for, such as .values({ text: `Default text for ${id.received}` }).
Another solution I have in mind would be to make .as accept an additional defaultValue argument like so: form.fields.text.as("text", `Default text for ${id.received}`). This feels more ergonomic in my opinion; however, since the fields seem to be controlled by states, I don't think it's as viable as the aforementioned.
Thank you!
Reproduction
https://github.com/DuCanhGH/sv-reprod-remote-reactive
Logs
This reference only captures the initial value of `form`. Did you mean to reference it inside a closure instead?
https://svelte.dev/e/state_referenced_locally svelte (state_referenced_locally)
Uncaught Svelte error: state_unsafe_mutation
Updating state inside `$derived(...)`, `$inspect(...)` or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
https://svelte.dev/e/state_unsafe_mutationSystem Info
System:
OS: Linux 6.18 cpe:/o:nixos:nixos:26.05 26.05 (Yarara)
CPU: (16) x64 AMD Ryzen 7 9800X3D 8-Core Processor
Memory: 14.85 GB / 30.51 GB
Container: Yes
Shell: 4.5.0 - /run/current-system/sw/bin/fish
Binaries:
Node: 22.22.0 - /run/current-system/sw/bin/node
Yarn: 1.22.22 - /run/current-system/sw/bin/yarn
npm: 10.9.4 - /run/current-system/sw/bin/npm
pnpm: 10.29.1 - /run/current-system/sw/bin/pnpm
Browsers:
Chromium: 145.0.7632.116
Firefox: 148.0
Firefox Developer Edition: 148.0
npmPackages:
@sveltejs/adapter-auto: ^7.0.0 => 7.0.1
@sveltejs/kit: ^2.50.2 => 2.53.4
@sveltejs/vite-plugin-svelte: ^6.2.4 => 6.2.4
svelte: ^5.51.0 => 5.53.7
vite: ^7.3.1 => 7.3.1Severity
annoyance
Additional Information
No response
Activity
einarpersson commentedon Jun 3, 2026
I think it would be nice if it was possible to set the initial value on the form in the markup close to where you'd put enhance, something like
<form ...editUser.set(user).enhance(...but now.setlives on the.fieldsproperty so I don't know.. But yeah, it would be nice to be able to do this without any script tagmgrubinger commentedon Jun 3, 2026
This should work if you use the value as the second argument to
text.as('text', YOUR-VALUE-HERE)in the latest SvelteKit.See https://svelte.dev/docs/kit/remote-functions#form-Fields:~:text=For%20most%20input%20types%2C%20you%20can%20also%20pass%20a%20second%20argument%20%E2%80%94%20.as(type%2C%20value)%20%E2%80%94%20to%20control%20the%20rendered%20value%3A and the PR #15577
I quickly tested it with your linked repo (with @sveltejs/kit@2.62.0 + svelte@5.56.1) and it seems to work if you just remove the
.set(..)calls and instead spread it like this onto the input{...form.fields.text.as("text",Default text for ${id.received})}(However, it's possible I'm missing something from your usecase)
DuCanhGH commentedon Jun 3, 2026
@mgrubinger nice, thanks! I didn't notice the fix because my issue was not linked in the PR. Closing this one.