Form controls on the server
A browser takes the state of a control from different places, and the server writes each one where the browser reads it, so the HTML alone (and a form posted without…
A browser takes the state of a control from different places, and the server writes each one where the
browser reads it, so the HTML alone (and a form posted without JavaScript) already carries the signal's
state, and hydrate finds nothing to repair:
| Control | JSX | Server markup |
|---|---|---|
text, number, ... input |
value={x()} |
value="..." attribute |
| checkbox, radio | checked={x()} |
bare checked attribute when truthy |
textarea |
value={x()} (or defaultValue) |
the escaped text as content; a first line break is kept |
select |
value={x()} (or defaultValue) |
selected on the matching <option> |
select multiple |
value={list()} |
selected on every option the array lists |
A browser ignores a value attribute on <textarea> and <select>, so the server never writes one.
An option matches by its value attribute, or by its text when it has none (whitespace collapsed), as
select.value does in the browser. A single value marks the first matching option; an array marks every
listed option (use arrays only with multiple). null and undefined stand for the empty string. Other
options lose a selected they were written with, because the control is controlled. The search covers
<optgroup>, <For> output, components and spread options, and it compares values without ever writing
them, so a hostile value cannot add markup. The same holds when the control is written through a spread
(<select {...props}>). On the client, an array value on a multiple select selects exactly the listed
options, and a value that matches no option leaves nothing selected there while the server HTML falls
back to the browser's default (the first option).
import { renderToString } from "@shadow-js/web/server";
const show = (view: () => unknown): string =>
renderToString(view).replace(/ data-hk="[^"]*"/g, "");
const color = "green";
const tags = ["a", "Cy"];
const bio = "line 1\n<b>";
console.log(
show(() => (
<select name="color" value={color}>
<option value="red">Red</option>
<option value="green">Green</option>
</select>
)),
);
console.log(
show(() => (
<select name="tags" multiple value={tags}>
<option value="a">Ada</option>
<option value="b">Bob</option>
<option>Cy</option>
</select>
)),
);
console.log(show(() => <textarea name="bio" value={bio} />));
<select name="color"><option value="red">Red</option><option value="green" selected>Green</option></select>
<select name="tags" multiple><option value="a" selected>Ada</option><option value="b">Bob</option><option selected>Cy</option></select>
<textarea name="bio">line 1
<b></textarea>