[]previous and next page

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…

For agentsUpdated

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

server-controls.tsxtsx
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} />));
Expected output
<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>