diff --git a/.changeset/fix-ssr-textarea-spread-value.md b/.changeset/fix-ssr-textarea-spread-value.md new file mode 100644 index 000000000..178914f1a --- /dev/null +++ b/.changeset/fix-ssr-textarea-spread-value.md @@ -0,0 +1,5 @@ +--- +"@solidjs/web": patch +--- + +Render textarea values supplied through SSR spreads as text content rather than invalid HTML attributes. diff --git a/packages/web/src/server.ts b/packages/web/src/server.ts index f824aeef5..210c6cd96 100644 --- a/packages/web/src/server.ts +++ b/packages/web/src/server.ts @@ -3569,6 +3569,15 @@ export function ssrElement(tag, props, children, needsId) { let result = `<${tag}${hk} `; for (let i = 0; i < keys.length; i++) { const prop = keys[i]; + const value = props[prop]; + // The compiler moves static textarea values into children, but an + // element with a spread is serialized here instead. Keep the runtime + // path equivalent: textarea value/defaultValue are its text content, + // never HTML attributes. + if (tag === "textarea" && (prop === "value" || prop === "defaultValue")) { + if (value !== null) children = escape(value); + continue; + } if (ChildProperties.has(prop)) { if (children === undefined && !skipChildren) children = @@ -3577,7 +3586,6 @@ export function ssrElement(tag, props, children, needsId) { : escape(props[prop]); continue; } - const value = props[prop]; if (prop === "style") { result += `style="${ssrStyle(value)}"`; } else if (prop === "class") { diff --git a/packages/web/test/server/spread-function-source.spec.tsx b/packages/web/test/server/spread-function-source.spec.tsx index 1594a6b45..cc3957277 100644 --- a/packages/web/test/server/spread-function-source.spec.tsx +++ b/packages/web/test/server/spread-function-source.spec.tsx @@ -32,6 +32,37 @@ describe("SSR spread with function source (#2815)", () => { expect(html).toContain('id="y"'); }); + test("textarea value from props merged around a spread becomes text content (#3286)", () => { + const value = () => "something"; + const html = renderToString(() =>