Skip to content

Value of <select> is set before the children are created when using spread #1754

Open

Description

@ghalle

Describe the bug

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).

Your Example Website or App

https://playground.solidjs.com/anonymous/d2bf2e03-5e3b-4a9c-a5c0-37a62bbc8f9c

Steps to Reproduce the Bug or Issue

  1. Open the example
  2. Look at the result

Expected behavior

The second and third select boxes should have the same option selected.

Screenshots or Videos

No response

Platform

  • OS: Windows
  • Browser: Edge
  • Version: 113

Additional context

No response

Activity

  1. 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
  2. ryansolid commented on Jun 26, 2023

    @ryansolid
    Member

    Thanks. I see the problem. Right DOM Expression classifies things into few categories:

    1. declarations:
    • declarations, walks
    1. expressions
    • non-reactive expressions
    • spreads
    • children
    1. 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.

  3. westbrma commented on Jun 7, 2024

    @westbrma

    Having similar problem when trying to assign the value of each option to a TS enum

  4. TylerRick commented on Aug 18, 2026

    @TylerRick

    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.

    function Select(props: {
    	value: string;
    	onChange: (v: string) => void;
    	options: { value: string; label: string }[];
    	placeholder?: string;
    }) {
    	const rest = { ...omit(props, 'value', 'onChange', 'options', 'placeholder') };
    	return (
    		<select
    			id="lang"
    			value={props.value}
    			onChange={(e) => props.onChange(e.currentTarget.value)}
    			{...rest}
    		>
    			<Show when={props.placeholder}>
    				<option value="" disabled style={{ display: 'none' }}>
    					{props.placeholder}
    				</option>
    			</Show>
    			<For each={props.options}>{(o) => <option value={o.value}>{o.label}</option>}</For>
    		</select>
    	);
    }
    // <Select name="lang" placeholder="Choose a language" options={options} value={value()} onChange={setValue} />
    // with value() === 'fr'

    After hydration:

    document.getElementById('lang').value;             // "en"  ← the browser default, and it stays there
    document.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.

  5. yumemi-thomas commented on Sep 2, 2026

    @yumemi-thomas

    @ryansolid This issue is resolved in 2.0
    Repro: https://s.olid.uk/id/rPlH1LDdT1-AEMlYV2aMAA

  6. TylerRick commented on Sep 3, 2026

    @TylerRick

    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.

  7. scenerecap commented on Sep 7, 2026

    @scenerecap

    Still reproduces on playground.solidjs.com today (2026-09-07, Chromium 151, Solid 1.x):

    1. 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'.
    2. Page loads: Select B correctly shows '2'. Selects A and C both show blank (reset to first/empty option).
    3. Click each select to open the dropdown — A and C have '2' available but did not initialize to it; B is already on '2'.
    4. 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.

    Recording with timestamps: https://scenerecap.com/s/blx29zw2ld

  8. yumemi-thomas commented on Sep 8, 2026

    @yumemi-thomas

    @scenerecap Yes. I'm not sure this will get fixed in 1.x.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions