From e4e343229567081b8aa0721207783079976d7946 Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Wed, 27 Sep 2023 19:33:05 -0700 Subject: [PATCH] Reset controlled .checked upon hydration --- .../react-dom-bindings/src/client/ReactDOMInput.js | 2 +- .../react-dom/src/__tests__/ReactDOMInput-test.js | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMInput.js b/packages/react-dom-bindings/src/client/ReactDOMInput.js index 9570f25c84fee..8657531125b83 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMInput.js +++ b/packages/react-dom-bindings/src/client/ReactDOMInput.js @@ -292,7 +292,7 @@ export function initInput( typeof checkedOrDefault !== 'symbol' && !!checkedOrDefault; - if (isHydrating) { + if (isHydrating && checked == null) { // Detach .checked from .defaultChecked but leave user input alone node.checked = node.checked; } else { diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js index 6c4e55fb14407..aeb49f109c0a0 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js @@ -1300,23 +1300,22 @@ describe('ReactDOMInput', () => { // Currently, we don't fire onChange when hydrating assertLog([]); - // Strangely, we leave `b` checked even though we rendered A with - // checked={true} and B with checked={false}. Arguably this is a bug. - expect(a.checked).toBe(false); - expect(b.checked).toBe(true); + // Instead, we reset the form to match React state and you lose the user's input. + expect(a.checked).toBe(true); + expect(b.checked).toBe(false); expect(c.checked).toBe(false); expect(isCheckedDirty(a)).toBe(true); expect(isCheckedDirty(b)).toBe(true); expect(isCheckedDirty(c)).toBe(true); assertInputTrackingIsCurrent(container); - // If we click on C now though... + // If we click on C now... await act(async () => { setUntrackedChecked.call(c, true); dispatchEventOnNode(c, 'click'); }); - // then since C's onClick doesn't set state, A becomes rechecked. + // there's no change. assertLog(['click c']); expect(a.checked).toBe(true); expect(b.checked).toBe(false);