From 2740158746128ad2cc66394e32c49496ba5862f1 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Tue, 18 Nov 2025 14:52:13 +0100
Subject: [PATCH 1/3] Add fixture
---
.../src/components/fixtures/selects/index.js | 63 ++++++++++++++++++-
1 file changed, 60 insertions(+), 3 deletions(-)
diff --git a/fixtures/dom/src/components/fixtures/selects/index.js b/fixtures/dom/src/components/fixtures/selects/index.js
index 9356a3770ac98..012a3eea959da 100644
--- a/fixtures/dom/src/components/fixtures/selects/index.js
+++ b/fixtures/dom/src/components/fixtures/selects/index.js
@@ -3,6 +3,28 @@ import TestCase from '../../TestCase';
const React = window.React;
const ReactDOM = window.ReactDOM;
+const ReactDOMClient = window.ReactDOMClient;
+
+class ControlledSelect extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {value: 'bar'};
+ }
+
+ handleChange = event => {
+ this.setState({value: event.target.value});
+ };
+
+ render() {
+ return (
+
+ foo
+ bar
+ baz
+
+ );
+ }
+}
class SelectFixture extends React.Component {
state = {value: ''};
@@ -33,15 +55,24 @@ class SelectFixture extends React.Component {
}
_renderNestedSelect() {
- ReactDOM.render(
+ const element = (
Select a color
Red
Blue
Green
- ,
- this._nestedDOMNode
+
);
+ const container = this._nestedDOMNode;
+ if (ReactDOMClient === undefined) {
+ ReactDOM.render(element, container);
+ } else {
+ const root = ReactDOMClient.createRoot(container);
+ // eslint-disable-next-line no-undef -- queueMicrotask is supported in modern browsers
+ queueMicrotask(
+ ReactDOM.flushSync.bind(null, root.render.bind(root, element))
+ );
+ }
}
render() {
@@ -230,6 +261,32 @@ class SelectFixture extends React.Component {
select attribute assignment.
+
+
+
+ No options should be selected.
+
+
+
+
+
+
+
+ Notes: form.reset() resets each associated form
+ element to its default value. For HTMLInputElement that's the value
+ attribute. For HTMLSELECTElement it's option with defaultSelected
+ set to true.
+
+
+ Bug became more pressing when React introduced automatic form reset.
+
+
);
}
From e00e0517de248925a1eced59e574f20510fe1613 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Tue, 18 Nov 2025 14:55:48 +0100
Subject: [PATCH 2/3] [react-dom] Keep controlled `` value on form
reset
---
.../src/client/ReactDOMSelect.js | 33 ++++++++++++-------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/packages/react-dom-bindings/src/client/ReactDOMSelect.js b/packages/react-dom-bindings/src/client/ReactDOMSelect.js
index 00136aa8175b1..815358ad69867 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMSelect.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMSelect.js
@@ -80,29 +80,38 @@ function updateOptions(
if (options[i].selected !== selected) {
options[i].selected = selected;
}
- if (selected && setDefaultSelected) {
- options[i].defaultSelected = true;
+ if (setDefaultSelected) {
+ options[i].defaultSelected = selected;
}
}
} else {
// Do not set `select.value` as exact behavior isn't consistent across all
// browsers for all cases.
const selectedValue = toString(getToStringValue(propValue));
- let defaultSelected = null;
+ // We use null as a signal that an option is explicitly selected.
+ let defaultSelected: void | null | HTMLOptionElement = undefined;
for (let i = 0; i < options.length; i++) {
- if (options[i].value === selectedValue) {
+ const selected = options[i].value === selectedValue;
+ if (setDefaultSelected) {
+ options[i].defaultSelected = selected;
+ }
+ if (selected) {
options[i].selected = true;
- if (setDefaultSelected) {
- options[i].defaultSelected = true;
+ defaultSelected = null;
+ if (!setDefaultSelected) {
+ // We need to loop through all options when updating defaultSelected.
+ // Otherwise multiple options may end up with defaultSelected=true
+ // and resetting won't work properly.
+ return;
}
- return;
}
- if (defaultSelected === null && !options[i].disabled) {
+ if (defaultSelected === undefined && !options[i].disabled) {
defaultSelected = options[i];
}
}
- if (defaultSelected !== null) {
+ if (defaultSelected !== null && defaultSelected !== undefined) {
defaultSelected.selected = true;
+ defaultSelected.defaultSelected = true;
}
}
}
@@ -152,7 +161,7 @@ export function initSelect(
const node: HTMLSelectElement = (element: any);
node.multiple = !!multiple;
if (value != null) {
- updateOptions(node, !!multiple, value, false);
+ updateOptions(node, !!multiple, value, true);
} else if (defaultValue != null) {
updateOptions(node, !!multiple, defaultValue, true);
}
@@ -221,7 +230,7 @@ export function updateSelect(
const node: HTMLSelectElement = (element: any);
if (value != null) {
- updateOptions(node, !!multiple, value, false);
+ updateOptions(node, !!multiple, value, true);
} else if (!!wasMultiple !== !!multiple) {
// For simplicity, reapply `defaultValue` if `multiple` is toggled.
if (defaultValue != null) {
@@ -238,6 +247,6 @@ export function restoreControlledSelectState(element: Element, props: Object) {
const value = props.value;
if (value != null) {
- updateOptions(node, !!props.multiple, value, false);
+ updateOptions(node, !!props.multiple, value, true);
}
}
From cacd6bab520d350b53bfba4d56e13dbcbe03e325 Mon Sep 17 00:00:00 2001
From: Sebastian Sebbie Silbermann
Date: Tue, 18 Nov 2025 16:57:03 +0100
Subject: [PATCH 3/3] Adjust warning
---
packages/react-dom-bindings/src/client/ReactDOMOption.js | 2 +-
packages/react-dom/src/__tests__/ReactDOMSelect-test.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/react-dom-bindings/src/client/ReactDOMOption.js b/packages/react-dom-bindings/src/client/ReactDOMOption.js
index ae7a7ba999c06..bf224bde06dd2 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMOption.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMOption.js
@@ -56,7 +56,7 @@ export function validateOptionProps(element: Element, props: Object) {
if (props.selected != null && !didWarnSelectedSetOnOption) {
console.error(
'Use the `defaultValue` or `value` props on instead of ' +
- 'setting `selected` on .',
+ 'setting `selected` on . Otherwise form reset may not work as expected.',
);
didWarnSelectedSetOnOption = true;
}
diff --git a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
index f05fb3e372697..6bc626c39410e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
@@ -792,7 +792,7 @@ describe('ReactDOMSelect', () => {
});
assertConsoleErrorDev([
'Use the `defaultValue` or `value` props on instead of ' +
- 'setting `selected` on .\n' +
+ 'setting `selected` on . Otherwise form reset may not work as expected.\n' +
' in option (at **)\n' +
' in App (at **)',
]);