샘플
react-dom 19.0.0: Handle React 19 form action function serialization, client submit replay script injection, useFormStatus SSR semantics, and controlled form input transformations in react-dom/server
검증된 샘플 — npm react-dom 19.0.0: Handle React 19 form action function serialization, client submit replay script injection, useFormStatus SSR semantics, and…
sha256:5dd0e802e26ddbefbd3dae3bb05f048113433235e8420b747c445e0bde6b5819
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 2
- 빌드한 서명 키
- 2
선언된 환경
node linux x64 node node npm
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-16 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
케이스
HOW- 목표
- Handle React 19 form action function serialization, client submit replay script injection, useFormStatus SSR semantics, and controlled form input transformations in react-dom/server
- 심벌
-
- react-dom/server.renderToString
- react-dom.useFormStatus
- 환경
- node
- 생성일
- 2026-08-16T12:39:54Z
컨트랙트
- Passing a function to form action or button formAction in react-dom/server emits a javascript:throw error URL and injects an inline submit listener replay script
- Passing a string URL to form action renders the string attribute verbatim and omits the reactFormReplay script
- useFormStatus during server rendering always returns pending false, data null, method null, and action null across parent and child components
- react-dom/server transforms controlled select value props into selected attributes on matching child options while stripping value from select
- react-dom/server renders controlled textarea value props as inner text content and strips the value attribute from the textarea tag
- react-dom/server strips suppressHydrationWarning attributes from server-rendered markup while preserving boolean data and aria attributes
파일
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/index.mjs
- test/contract.mjs
소스
# React DOM Form Actions & SSR Form Serialization
## What `search_known_solution` Answered
`search_known_solution` answered with existing samples for `react-dom`:
1. `sha256:802942a98d62b825180c8f2987341180be283e9fadd47ebdd7ea909279f09ee0` — React 19 Node vs Web stream entrypoints in `react-dom/server` & `react-dom/static`, async component crash in `renderToString`, and `react-server` condition export constraints.
2. `sha256:ffbf1189d6c67b17effd53b4f30bb520e3a01b2c792450fda6198ef4c832f34e` — Document metadata hoisting, stylesheet precedence deduplication, and resource hints in `react-dom/server`.
3. `sha256:552ef5d3a70a3b9900979b25b8242bc2f67de724201eef3d5255996d2cbff54f` — Function component `defaultProps` removal and native `ref` prop passing.
4. `sha256:059011629c7734f8ac38604635b6d097a2b80c286715c0bbcc0db022a73afa54` — Runtime failures across duplicate React instances from peer dependency resolution.
5. `sha256:a8e74b80a92c127ce2b29ee4d261faaea5cb092cc5869188facceec927b1a737` — Development vs production runtime contracts across `NODE_ENV`.
This contract targets a distinct trap: the interaction between React 19 Form Actions (`action={fn}`, `formAction={fn}`), `useFormStatus` SSR semantics, and controlled form element serialization (`<select value>`, `<textarea value>`) in `react-dom/server`.
## What a Model Would Have Written
A model expects `react-dom/server.renderToString` to strip function props passed to `action` and `formAction` attributes (treating them like standard client event handlers such as `onClick`), to preserve `<select value="...">` and `<textarea value="...">` attributes verbatim in HTML output, and for `suppressHydrationWarning` to remain on rendered tags.
## How the Wrong Version Fails
Fails silently with a green build: `react-dom/server` serializes function actions to `javascript:throw new Error(...)` and automatically appends an inline `submit` listener replay script (`$$reactFormReplay`), while `<select value>` and `<textarea value>` silently rewrite child DOM options and inner text, breaking container snapshot tests and pre-hydration expectations.
{"case":{"believed":"react-dom/server strips functions passed to form action and button formAction props during server-side rendering like standard event handlers, leaving form submission to native browser HTTP request defaults.","caseId":"case:sha256:6851df1f9fad71692611d5d1dd099c5f6bcd6deb676388250252b8f52e1a6931","contract":["Passing a function to form action or button formAction in react-dom/server emits a javascript:throw error URL and injects an inline submit listener replay script","Passing a string URL to form action renders the string attribute verbatim and omits the reactFormReplay script","useFormStatus during server rendering always returns pending false, data null, method null, and action null across parent and child components","react-dom/server transforms controlled select value props into selected attributes on matching child options while stripping value from select","react-dom/server renders controlled textarea value props as inner text content and strips the value attribute from the textarea tag","react-dom/server strips suppressHydrationWarning attributes from server-rendered markup while preserving boolean data and aria attributes"],"goal":"Handle React 19 form action function serialization, client submit replay script injection, useFormStatus SSR semantics, and controlled form input transformations in react-dom/server","kind":"HOW","packages":["pkg:npm/react-dom@19.0.0","pkg:npm/react@19.0.0"],"schemaVersion":1,"symbols":["react-dom/server.renderToString","react-dom.useFormStatus"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/react-dom@19.0.0","pkg:npm/react@19.0.0"],"schemaVersion":1,"symbols":["react-dom/server.renderToString","react-dom.useFormStatus"],"verifierAdapter":"node-typescript@1"}
{
"name": "npm-react-dom-d4",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "npm-react-dom-d4",
"version": "1.0.0",
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
},
"node_modules/react": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.25.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT"
}
}
}
{
"name": "npm-react-dom-d4",
"version": "1.0.0",
"type": "module",
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { useFormStatus } from 'react-dom';
/**
* Child component that queries useFormStatus from inside a form.
*/
export function StatusDisplay() {
const status = useFormStatus();
return React.createElement('span', { id: 'status-indicator' }, JSON.stringify(status));
}
/**
* Component tree demonstrating React 19 Form Actions, useFormStatus boundaries,
* controlled form input transforms, and attribute serialization in react-dom/server.
*/
export function renderActionForm() {
function handleFormSubmit(formData) {}
function handleButtonSubmit(formData) {}
function ActionFormWrapper() {
const outerStatus = useFormStatus();
return React.createElement('div', { id: 'container' },
React.createElement('span', { id: 'outer-status' }, JSON.stringify(outerStatus)),
React.createElement('form', {
id: 'main-form',
action: handleFormSubmit,
suppressHydrationWarning: true
},
React.createElement(StatusDisplay),
React.createElement('select', { value: 'choice-b', id: 'select-input', onChange: () => {} },
React.createElement('option', { value: 'choice-a', selected: true }, 'Choice A'),
React.createElement('option', { value: 'choice-b' }, 'Choice B')
),
React.createElement('textarea', { value: 'prefilled-notes', id: 'textarea-input', readOnly: true }),
React.createElement('input', { type: 'checkbox', checked: true, readOnly: true, id: 'chk-on' }),
React.createElement('input', { type: 'checkbox', checked: false, readOnly: true, id: 'chk-off' }),
React.createElement('div', {
id: 'attr-div',
'data-active': false,
'aria-hidden': true,
contentEditable: false,
suppressHydrationWarning: true
}),
React.createElement('button', {
type: 'submit',
formAction: handleButtonSubmit,
id: 'submit-btn'
}, 'Submit')
)
);
}
return ReactDOMServer.renderToString(React.createElement(ActionFormWrapper));
}
/**
* Renders a form with URL string actions instead of function actions.
*/
export function renderStaticForm() {
function StaticFormWrapper() {
return React.createElement('form', {
id: 'static-form',
action: 'https://example.com/api/submit'
},
React.createElement('button', {
type: 'submit',
formAction: 'https://example.com/api/override',
id: 'static-btn'
}, 'Post')
);
}
return ReactDOMServer.renderToString(React.createElement(StaticFormWrapper));
}
import assert from 'node:assert/strict';
import { renderActionForm, renderStaticForm } from '../src/index.mjs';
// --- CONTRACT SUITE: React 19 Form Actions, useFormStatus SSR semantics, & Form Input Serialization ---
// Test 1: Function Form Actions serialization and client replay script injection
{
const html = renderActionForm();
// Assertion 1.1: Function passed to form action is serialized as javascript:throw error URL
assert.ok(
html.includes('action="javascript:throw new Error('React form unexpectedly submitted.')"'),
`Function action prop must be serialized as javascript:throw error URL: ${html}`
);
// Assertion 1.2: Function passed to button formAction is serialized as javascript:throw error URL
assert.ok(
html.includes('formAction="javascript:throw new Error('React form unexpectedly submitted.')"'),
`Function formAction prop must be serialized as javascript:throw error URL: ${html}`
);
// Assertion 1.3: react-dom/server automatically injects submit event replay listener script
assert.ok(
html.includes('<script>addEventListener("submit",function(a){'),
`Server rendering with function actions must inject submit replay script: ${html}`
);
assert.ok(
html.includes('$$reactFormReplay'),
`Injected script must register form submissions to $$reactFormReplay: ${html}`
);
}
// Test 2: String Form Actions render verbatim without replay script injection
{
const html = renderStaticForm();
// Assertion 2.1: String action URL is rendered verbatim as attribute
assert.ok(
html.includes('action="https://example.com/api/submit"'),
`String action URL must be rendered verbatim: ${html}`
);
// Assertion 2.2: String formAction URL is rendered verbatim as attribute
assert.ok(
html.includes('formAction="https://example.com/api/override"') || html.includes('formaction="https://example.com/api/override"'),
`String formAction URL must be rendered verbatim: ${html}`
);
// Assertion 2.3: Replay script is NOT injected for string actions
assert.equal(
html.includes('$$reactFormReplay'),
false,
`Static forms with string actions must not inject replay script: ${html}`
);
}
// Test 3: useFormStatus SSR semantics across parent and child components
{
const html = renderActionForm();
// Assertion 3.1: useFormStatus called outside form returns default pending:false, data:null, method:null, action:null
assert.ok(
html.includes('<span id="outer-status">{"pending":false,"data":null,"method":null,"action":null}</span>'),
`useFormStatus outside form must return null/false status during SSR: ${html}`
);
// Assertion 3.2: useFormStatus called inside form during SSR also returns pending:false, data:null, method:null, action:null
assert.ok(
html.includes('<span id="status-indicator">{"pending":false,"data":null,"method":null,"action":null}</span>'),
`useFormStatus inside form must return null/false status during SSR: ${html}`
);
}
// Test 4: Controlled select value transformation and child option selected injection
{
const html = renderActionForm();
// Assertion 4.1: select element strips value prop and injects selected="" onto matching child option
assert.ok(
html.includes('<select id="select-input"><option value="choice-a">Choice A</option><option value="choice-b" selected="">Choice B</option></select>'),
`Controlled select value must inject selected="" into matching option and strip value from select: ${html}`
);
}
// Test 5: Controlled textarea value transformation to inner text content
{
const html = renderActionForm();
// Assertion 5.1: textarea value prop is stripped from attributes and emitted as inner text
const textareaMatch = html.match(/<textarea[^>]*id="textarea-input"[^>]*>prefilled-notes<\/textarea>/);
assert.ok(
textareaMatch,
`Controlled textarea value must be emitted as inner text: ${html}`
);
assert.equal(
textareaMatch[0].includes('value='),
false,
`Controlled textarea must strip value attribute: ${textareaMatch[0]}`
);
}
// Test 6: Boolean attribute handling, data/aria preservation, and suppressHydrationWarning stripping
{
const html = renderActionForm();
// Assertion 6.1: checked=true renders checked="", checked=false omits checked attribute
const checkedMatch = html.match(/<input[^>]*id="chk-on"[^>]*\/>/);
assert.ok(checkedMatch, 'Checked checkbox must be present');
assert.ok(checkedMatch[0].includes('checked=""'), `checked=true must render checked="": ${checkedMatch[0]}`);
const uncheckedMatch = html.match(/<input[^>]*id="chk-off"[^>]*\/>/);
assert.ok(uncheckedMatch, 'Unchecked checkbox must be present');
assert.equal(uncheckedMatch[0].includes('checked=""'), false, `checked=false must omit checked attribute: ${uncheckedMatch[0]}`);
// Assertion 6.2: suppressHydrationWarning is stripped completely from HTML
assert.equal(
html.includes('suppressHydrationWarning'),
false,
`suppressHydrationWarning must be stripped from server-rendered HTML: ${html}`
);
// Assertion 6.3: data-*, aria-*, and enumerated attributes preserve string representations
assert.ok(
html.includes('data-active="false"'),
`data-active=false must be rendered as string data-active="false": ${html}`
);
assert.ok(
html.includes('aria-hidden="true"'),
`aria-hidden=true must be rendered as string aria-hidden="true": ${html}`
);
assert.ok(
html.includes('contentEditable="false"'),
`contentEditable=false must be rendered as contentEditable="false": ${html}`
);
}
console.log('Contract passed successfully.');