diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js
index fc4594261eba2..135c84639a4e9 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 3c9e802b6e34e..e089b33098520 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 f0bf363fc68d0..e2b628e39b7a5 100644
--- a/packages/react-dom/src/client/ReactDOMFiberInput.js
+++ b/packages/react-dom/src/client/ReactDOMFiberInput.js
@@ -205,27 +205,29 @@ 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;
}
}