You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Value of <select> is set before the children are created when using spread #1754
When using spread on a select element the value will be set before the children are created and this will cause the value to be set to an empty string (this is a browser behavior because no option match).
changed the title [-]Value of `<select>` is set after the children are created when using spread[/-][+]Value of `<select>` is set before the children are created when using spread[/+]on Jun 3, 2023
Thanks. I see the problem. Right DOM Expression classifies things into few categories:
declarations:
declarations, walks
expressions
non-reactive expressions
spreads
children
dynamics
reactive expressions
And get inserted in that order.
Spreads... are clearly in the wrong category. Or rather they straddle both categories. The reason for their location historically was so that refs could be set before children. Moving them to dynamic suggests that even static expressions are dynamic (ie... after children). I think that is fair. Ultimately I wonder if spreads should have 2 phases. Or alternatively refs would always be set at the end. Downside of the latter is no easy pass down. The former seems better but it will take a little bit of work. A quick fix here will break something else. So this will probably need a minor or major release.
Still reproduces on solid-js / @solidjs/web 2.0.0-rc.0, and SSR makes it permanent rather than transient.
Shape: a Select component that forwards rest-props ({...rest} on the <select>), renders its options through <For>, and is server-rendered then hydrated.
document.getElementById('lang').value;// "en" ← the browser default, and it stays theredocument.getElementById('state').textContent;// "signal: fr"
Delete the {...rest} spread from the <select> and hydration lands on fr correctly — everything else unchanged. So the spread is still applying value before the <For> children exist, and in a hydration pass nothing comes along later to re-apply it.
Separately, the server markup is wrong even without the spread: SSR emits the bound value as a value attribute on the <select> and marks no option selected, so the pre-hydration page always shows the first option. I filed that as a separate issue: #3013.
Full runnable repro (vite 8.2.1 + vite-plugin-solid 3.0.0-next.27, renderToStream + hydrate) is attached to that issue; it demonstrates both halves.
Confirmed against Solid 2.0.0-rc.6 with the exact component shape from my earlier rc.0 follow-up: a reactive value, forwarded {...rest}, a hidden placeholder, and options rendered through <For>.
Without per-option selected, a synchronous read immediately after rendering still sees the browser default ("af" for a value of "en"). After the runtime's queued microtask, however, the select correctly reads "en". The empty-value placeholder similarly settles from "af" to "", with the placeholder option selected. The same initial-value case also passes in Chromium.
The jsdom test that initially made me think this still reproduced was asserting before that microtask. In the post-render/user-visible state, my earlier client-side reproduction is resolved. From my side, #1754 can be closed. Thanks for providing the updated repro.
Still reproduces on playground.solidjs.com today (2026-09-07, Chromium 151, Solid 1.x):
Open the playground snippet with three <select> elements — A (spread attributes), B (control, direct value prop), C (dynamic <For> children without the cast trick). All three are wired to the same signal with value '2'.
Page loads: Select B correctly shows '2'. Selects A and C both show blank (reset to first/empty option).
Click each select to open the dropdown — A and C have '2' available but did not initialize to it; B is already on '2'.
The console logs DOMNode._children is expected to not be null on load — visible in DevTools at t=0.
Root cause appears to be that value is applied (via spread or the runtime's child-insertion path) before the <For>-generated <option> elements are mounted, so the browser resets to the first option.
Activity
[-]Value of `<select>` is set after the children are created when using spread[/-][+]Value of `<select>` is set before the children are created when using spread[/+]ryansolid commentedon Jun 26, 2023
Thanks. I see the problem. Right DOM Expression classifies things into few categories:
And get inserted in that order.
Spreads... are clearly in the wrong category. Or rather they straddle both categories. The reason for their location historically was so that refs could be set before children. Moving them to dynamic suggests that even static expressions are dynamic (ie... after children). I think that is fair. Ultimately I wonder if spreads should have 2 phases. Or alternatively refs would always be set at the end. Downside of the latter is no easy pass down. The former seems better but it will take a little bit of work. A quick fix here will break something else. So this will probably need a minor or major release.
westbrma commentedon Jun 7, 2024
Having similar problem when trying to assign the value of each option to a TS enum
TylerRick commentedon Aug 18, 2026
Still reproduces on solid-js / @solidjs/web 2.0.0-rc.0, and SSR makes it permanent rather than transient.
Shape: a
Selectcomponent that forwards rest-props ({...rest}on the<select>), renders its options through<For>, and is server-rendered then hydrated.After hydration:
Delete the
{...rest}spread from the<select>and hydration lands onfrcorrectly — everything else unchanged. So the spread is still applyingvaluebefore the<For>children exist, and in a hydration pass nothing comes along later to re-apply it.Separately, the server markup is wrong even without the spread: SSR emits the bound value as a
valueattribute on the<select>and marks no optionselected, so the pre-hydration page always shows the first option. I filed that as a separate issue: #3013.Full runnable repro (vite 8.2.1 + vite-plugin-solid 3.0.0-next.27,
renderToStream+hydrate) is attached to that issue; it demonstrates both halves.yumemi-thomas commentedon Sep 2, 2026
@ryansolid This issue is resolved in 2.0
Repro: https://s.olid.uk/id/rPlH1LDdT1-AEMlYV2aMAA
TylerRick commentedon Sep 3, 2026
Confirmed against Solid 2.0.0-rc.6 with the exact component shape from my earlier rc.0 follow-up: a reactive
value, forwarded{...rest}, a hidden placeholder, and options rendered through<For>.Without per-option
selected, a synchronous read immediately after rendering still sees the browser default ("af"for a value of"en"). After the runtime's queued microtask, however, the select correctly reads"en". The empty-value placeholder similarly settles from"af"to"", with the placeholder option selected. The same initial-value case also passes in Chromium.The jsdom test that initially made me think this still reproduced was asserting before that microtask. In the post-render/user-visible state, my earlier client-side reproduction is resolved. From my side, #1754 can be closed. Thanks for providing the updated repro.
scenerecap commentedon Sep 7, 2026
Still reproduces on playground.solidjs.com today (2026-09-07, Chromium 151, Solid 1.x):
<select>elements — A (spread attributes), B (control, direct value prop), C (dynamic<For>children without the cast trick). All three are wired to the same signal with value'2'.'2'. Selects A and C both show blank (reset to first/empty option).'2'available but did not initialize to it; B is already on'2'.DOMNode._children is expected to not be nullon load — visible in DevTools at t=0.Root cause appears to be that
valueis applied (via spread or the runtime's child-insertion path) before the<For>-generated<option>elements are mounted, so the browser resets to the first option.Recording with timestamps: https://scenerecap.com/s/blx29zw2ld
yumemi-thomas commentedon Sep 8, 2026
@scenerecap Yes. I'm not sure this will get fixed in 1.x.