Skip to content

fix: keep remote form radio/checkbox selections in sync after submit - #16920

Closed
marwan562 wants to merge 1 commit into
sveltejs:version-3from
marwan562:fix/16820-remote-form-control-sync
Closed

marwan562 wants to merge 1 commit into
sveltejs:version-3from
marwan562:fix/16820-remote-form-control-sync

Conversation

@marwan562

Copy link
Copy Markdown
Contributor

Summary

Closes #16820

After a successful same-URL remote form() submission, the default enhance resets the element (#14322). Text inputs survived this because .as('text', liveQueryValue) keeps their defaultValue in sync with field state, so reset() landed on the persisted value. Radio, checkbox-array and single-checkbox inputs had no equivalent reset-target channel: radio's .as second argument is the option identity, single-checkbox defaultChecked was a static second argument (usually omitted), so controls snapped back to first-render defaults and fields.value() went stale.

Fix (packages/kit/src/runtime/form-utils.js):

  • radio / checkbox-array spreads now also emit defaultChecked, computed by the same is_checked() closure as checked — the reset target always mirrors current field state
  • single-checkbox defaultChecked now follows get_value() ?? input_value (its existing checked computation) instead of the static second argument

Since defaults now track state, the post-submit reset() becomes a visual no-op for these controls and handle_reset's FormData snapshot re-seeds fields.value() with the correct data — text's existing mechanism, generalized. The public types already declared defaultChecked for checkbox/radio fields, so no type surface changes.

Out of scope (flagging explicitly)

  • select still reverts to its first option after the post-submission reset — its default selection lives on <option selected> attributes, which can't be expressed through the <select> element spread. Needs separate design work.
  • text/number/etc. with a static literal second argument (.as('text', 'constant')) retain their pre-fix behavior — only reactive/live values survive the reset.

Testing

  • new route packages/kit/test/apps/async/src/routes/remote/form/radio-reset/ + two Playwright cases:
    • changed radio + checkbox-array + single-checkbox selections all survive the default post-success reset; fields.value() stays in sync
    • failed validation (missing required radio) does not reset; completing the submission then persists and preserves selection
  • client.test.js: 92 passed (dev); new tests also pass on the build project; cross-project form works incl. no-JS SSR green
  • pnpm run lint / pnpm run format clean; changeset included

radio/array-checkbox inputs now emit a defaultChecked prop mirroring
checked, and single-checkbox defaultChecked follows field state instead
of the static second argument. the browser reset() fired by the default
enhance after a successful same-URL submission therefore restores the
current field state rather than first-render defaults, so controls no
longer revert and fields.value() stays correct

closes sveltejs#16820
@pkg-svelte-dev

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 3a3dd26:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/3a3dd2680c84cafaddebbad67436a975de5ec485

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/16920

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3a3dd26

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Nic-Polumeyv

Copy link
Copy Markdown
Member

Unfortunately. This is incorrect.

@Nic-Polumeyv

Copy link
Copy Markdown
Member

Superseded by #16926. Mirroring checked into defaultChecked from client state turns every reset into a no-op for these inputs, including the post-success reset on create forms and <button type="reset">, and the non-enhanced path stays blank because a successful submission is stored without input. The replacement adds a third .as() argument for the current value, the same channel text inputs already use.

elliott-with-the-longest-name-on-github pushed a commit that referenced this pull request Aug 25, 2026
….) and .as('checkbox', ...) (#16926)

Depends on sveltejs/svelte#18701. CI is red because the pinned
svelte@5.56.8 only preserves `defaultChecked` through hydration for
`type="checkbox"`; for radios it strips the SSR `checked` attribute, so
`form.reset()` after a title-only save unchecks the selected radio. The
test asserts the correct behaviour and passes unchanged once #18701 is
released and the catalog is bumped; no kit code or test changes are
needed for that.

Text, select and single-checkbox inputs carry their second .as()
argument into defaultValue/defaultChecked, so the form.reset() the
default enhance runs after a successful submission
(client/remote-functions/form.svelte.js) lands them on the refreshed
current value. Radio and array checkbox inputs (runtime/form-utils.js)
have no such attribute because their second argument is the option
identity, so the reset clears them. Without JS the same happens
server-side where a successful submission is stored without input, so
the re-render has nothing to check.

This PR adds a third argument, the current value of the field:

```svelte
<script>
	import { get_settings, update_settings } from './settings.remote';

	const settings = $derived(await get_settings());
</script>

<form {...update_settings}>
	<input {...update_settings.fields.title.as('text', settings.title)} />

	{#each ['public', 'private'] as option}
		<label>
			<input {...update_settings.fields.visibility.as('radio', option, settings.visibility)} />
			{option}
		</label>
	{/each}

	{#each ['red', 'green', 'blue'] as tag}
		<label>
			<input {...update_settings.fields.tags.as('checkbox', tag, settings.tags)} />
			{tag}
		</label>
	{/each}

	<button>Save</button>
</form>
```

defaultChecked is computed from it, and checked from get_value() ??
current_value so the server render after a non-enhanced submission is
right too. Inputs without the third argument keep clearing on reset.


Supersedes #16920.

Fixes #16820
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remote functions: radios / fields.value() out of sync after same-URL form() success

2 participants