From 037b609c8f08fb8d43f623ca41137b56eb6a8df7 Mon Sep 17 00:00:00 2001 From: Zelys Date: Wed, 1 Apr 2026 18:04:37 -0500 Subject: [PATCH] Fix: has size > 1 (a list box), no option should be selected if the controlled value prop does not match any option value. The browser leaves list boxes with no selection in this case; React was incorrectly falling back to selecting the first non-disabled option on every update. The fix is one line: the first-option fallback in updateOptions now only runs when node.size <= 1 (a dropdown), leaving list boxes unselected as the HTML spec requires. Fixes #24469 --- .../src/client/ReactDOMSelect.js | 2 +- .../src/__tests__/ReactDOMSelect-test.js | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMSelect.js b/packages/react-dom-bindings/src/client/ReactDOMSelect.js index 00136aa8175b..c6aa69c748e5 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMSelect.js +++ b/packages/react-dom-bindings/src/client/ReactDOMSelect.js @@ -101,7 +101,7 @@ function updateOptions( defaultSelected = options[i]; } } - if (defaultSelected !== null) { + if (defaultSelected !== null && node.size <= 1) { defaultSelected.selected = true; } } diff --git a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js index c90455307d0f..980028ebc450 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js @@ -486,6 +486,63 @@ describe('ReactDOMSelect', () => { expect(select.selectedIndex).toBe(-1); }); + it('does not select the first option when size > 1 and value does not match', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render( + , + ); + }); + + const select = container.firstChild; + + // On initial render the browser handles list box selection (nothing selected). + // On subsequent renders updateOptions runs; it must not fall back to the + // first option for a list box (size > 1). + await act(() => { + root.render( + , + ); + }); + + expect(select.options[0].selected).toBe(false); + expect(select.options[1].selected).toBe(false); + expect(select.options[2].selected).toBe(false); + expect(select.selectedIndex).toBe(-1); + }); + + it('selects the matching option when size > 1 and value matches', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render( + , + ); + }); + + const select = container.firstChild; + + expect(select.options[0].selected).toBe(false); + expect(select.options[1].selected).toBe(true); + expect(select.options[2].selected).toBe(false); + expect(select.value).toBe('giraffe'); + }); + it('should remember value when switching to uncontrolled', async () => { const stub = (