From cc2d60958367657cd2bd8c5a16877bb5e615b510 Mon Sep 17 00:00:00 2001 From: Maxime Nory Date: Thu, 31 May 2018 09:50:26 +0200 Subject: [PATCH] add isHydrating argument and tests --- .../ReactDOMServerIntegrationForms-test.js | 30 +++++++++++++++++-- .../src/client/ReactDOMFiberComponent.js | 4 +-- .../src/client/ReactDOMFiberInput.js | 24 +++++++++------ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js index fc4594261eba..2d873d20c61e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js @@ -348,9 +348,13 @@ describe('ReactDOMServerIntegration', () => { ControlledSelect; beforeEach(() => { ControlledInput = class extends React.Component { + static defaultProps = { + type: 'text', + initialValue: 'Hello', + }; constructor() { - super(); - this.state = {value: 'Hello'}; + super(...arguments); + this.state = {value: this.props.initialValue}; } handleChange(event) { if (this.props.onChange) { @@ -361,6 +365,7 @@ describe('ReactDOMServerIntegration', () => { render() { return ( @@ -551,6 +556,27 @@ describe('ReactDOMServerIntegration', () => { expect(changeCount).toBe(0); }); + it('should not blow away user-interaction on successful reconnect to an uncontrolled range input', () => + testUserInteractionBeforeClientRender( + , + '0.5', + '1', + )); + + it('should not blow away user-interaction on successful reconnect to a controlled range input', async () => { + let changeCount = 0; + await testUserInteractionBeforeClientRender( + changeCount++} + />, + '0.25', + '1', + ); + expect(changeCount).toBe(0); + }); + it('should not blow away user-entered text on successful reconnect to an uncontrolled checkbox', () => testUserInteractionBeforeClientRender( , diff --git a/packages/react-dom/src/client/ReactDOMFiberComponent.js b/packages/react-dom/src/client/ReactDOMFiberComponent.js index 3c9e802b6e34..e089b3309852 100644 --- a/packages/react-dom/src/client/ReactDOMFiberComponent.js +++ b/packages/react-dom/src/client/ReactDOMFiberComponent.js @@ -530,7 +530,7 @@ export function setInitialProperties( // TODO: Make sure we check if this is still unmounted or do any clean // up necessary since we never stop tracking anymore. inputValueTracking.track((domElement: any)); - ReactDOMFiberInput.postMountWrapper(domElement, rawProps); + ReactDOMFiberInput.postMountWrapper(domElement, rawProps, false); break; case 'textarea': // TODO: Make sure we check if this is still unmounted or do any clean @@ -1077,7 +1077,7 @@ export function diffHydratedProperties( // TODO: Make sure we check if this is still unmounted or do any clean // up necessary since we never stop tracking anymore. inputValueTracking.track((domElement: any)); - ReactDOMFiberInput.postMountWrapper(domElement, rawProps); + ReactDOMFiberInput.postMountWrapper(domElement, rawProps, true); break; case 'textarea': // TODO: Make sure we check if this is still unmounted or do any clean diff --git a/packages/react-dom/src/client/ReactDOMFiberInput.js b/packages/react-dom/src/client/ReactDOMFiberInput.js index f0bf363fc68d..65fec82b45e4 100644 --- a/packages/react-dom/src/client/ReactDOMFiberInput.js +++ b/packages/react-dom/src/client/ReactDOMFiberInput.js @@ -205,27 +205,33 @@ export function updateWrapper(element: Element, props: Object) { } } -export function postMountWrapper(element: Element, props: Object) { +export function postMountWrapper( + element: Element, + props: Object, + isHydrating: boolean, +) { const node = ((element: any): InputWithWrapperState); if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) { const initialValue = '' + node._wrapperState.initialValue; - - // With range inputs node.value may be a default value calculated from the - // min/max attributes. This ensures that node.value is set with the correct - // value coming from props. - const currentValue = props.type === 'range' ? '' : node.value; + let currentValue; + if (isHydrating) { + currentValue = node.value; + } else { + // With range inputs node.value may be a default value calculated from the + // min/max attributes. This ensures that node.value is set with the correct + // value coming from props. + currentValue = props.type === 'range' ? '' : node.value; + } // Do not assign value if it is already set. This prevents user text input // from being lost during SSR hydration. - if (currentValue === '') { + if (!node.hasAttribute('value')) { // Do not re-assign the value property if there is no change. This // potentially avoids a DOM write and prevents Firefox (~60.0.1) from // prematurely marking required inputs as invalid if (initialValue !== currentValue) { node.value = initialValue; - } else if (props.type === 'range') { - node.value = initialValue; } }