diff --git a/packages/react-dom-bindings/src/client/ReactDOMInput.js b/packages/react-dom-bindings/src/client/ReactDOMInput.js
index 1416df94ebdc9..a1805c9e65a64 100644
--- a/packages/react-dom-bindings/src/client/ReactDOMInput.js
+++ b/packages/react-dom-bindings/src/client/ReactDOMInput.js
@@ -92,38 +92,17 @@ export function updateInputChecked(element: Element, props: Object) {
export function updateInput(element: Element, props: Object) {
const node: HTMLInputElement = (element: any);
- updateInputChecked(element, props);
-
const value = getToStringValue(props.value);
const type = props.type;
- if (value != null) {
- if (type === 'number') {
- if (
- // $FlowFixMe[incompatible-type]
- (value === 0 && node.value === '') ||
- // We explicitly want to coerce to number here if possible.
- // eslint-disable-next-line
- node.value != (value: any)
- ) {
- node.value = toString((value: any));
- }
- } else if (node.value !== toString((value: any))) {
- node.value = toString((value: any));
- }
- } else if (type === 'submit' || type === 'reset') {
- // Submit/reset inputs need the attribute removed completely to avoid
- // blank-text buttons.
- node.removeAttribute('value');
- return;
- }
-
if (disableInputAttributeSyncing) {
// When not syncing the value attribute, React only assigns a new value
// whenever the defaultValue React prop has changed. When not present,
// React does nothing
if (props.defaultValue != null) {
setDefaultValue(node, props.type, getToStringValue(props.defaultValue));
+ } else {
+ node.removeAttribute('value');
}
} else {
// When syncing the value attribute, the value comes from a cascade of
@@ -135,6 +114,8 @@ export function updateInput(element: Element, props: Object) {
setDefaultValue(node, props.type, value);
} else if (props.defaultValue != null) {
setDefaultValue(node, props.type, getToStringValue(props.defaultValue));
+ } else {
+ node.removeAttribute('value');
}
}
@@ -154,6 +135,29 @@ export function updateInput(element: Element, props: Object) {
node.defaultChecked = !!props.defaultChecked;
}
}
+
+ updateInputChecked(element, props);
+
+ if (value != null) {
+ if (type === 'number') {
+ if (
+ // $FlowFixMe[incompatible-type]
+ (value === 0 && node.value === '') ||
+ // We explicitly want to coerce to number here if possible.
+ // eslint-disable-next-line
+ node.value != (value: any)
+ ) {
+ node.value = toString((value: any));
+ }
+ } else if (node.value !== toString((value: any))) {
+ node.value = toString((value: any));
+ }
+ } else if (type === 'submit' || type === 'reset') {
+ // Submit/reset inputs need the attribute removed completely to avoid
+ // blank-text buttons.
+ node.removeAttribute('value');
+ return;
+ }
}
export function postInitInput(
diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
index bf523c4494e81..67d6f7bd465ab 100644
--- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
+++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
@@ -1166,11 +1166,7 @@ describe('DOMPropertyOperations', () => {
).toErrorDev(
'A component is changing a controlled input to be uncontrolled',
);
- if (disableInputAttributeSyncing) {
- expect(container.firstChild.hasAttribute('value')).toBe(false);
- } else {
- expect(container.firstChild.getAttribute('value')).toBe('foo');
- }
+ expect(container.firstChild.hasAttribute('value')).toBe(false);
expect(container.firstChild.value).toBe('foo');
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
index f829865efa5ba..47fca9522fac8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
@@ -1952,11 +1952,7 @@ describe('ReactDOMInput', () => {
expect(renderInputWithStringThenWithUndefined).toErrorDev(
'A component is changing a controlled input to be uncontrolled.',
);
- if (disableInputAttributeSyncing) {
- expect(input.getAttribute('value')).toBe(null);
- } else {
- expect(input.getAttribute('value')).toBe('latest');
- }
+ expect(input.getAttribute('value')).toBe(null);
});
it('preserves the value property', () => {
@@ -2002,11 +1998,7 @@ describe('ReactDOMInput', () => {
'or `undefined` for uncontrolled components.',
'A component is changing a controlled input to be uncontrolled.',
]);
- if (disableInputAttributeSyncing) {
- expect(input.hasAttribute('value')).toBe(false);
- } else {
- expect(input.getAttribute('value')).toBe('latest');
- }
+ expect(input.hasAttribute('value')).toBe(false);
});
it('preserves the value property', () => {
@@ -2165,4 +2157,30 @@ describe('ReactDOMInput', () => {
expect(node.hasAttribute('value')).toBe(false);
});
});
+
+ it('should remove previous `defaultValue`', () => {
+ const node = ReactDOM.render(
+ ,
+ container,
+ );
+
+ expect(node.value).toBe('0');
+ expect(node.defaultValue).toBe('0');
+
+ ReactDOM.render(, container);
+ expect(node.defaultValue).toBe('');
+ });
+
+ it('should treat `defaultValue={null}` as missing', () => {
+ const node = ReactDOM.render(
+ ,
+ container,
+ );
+
+ expect(node.value).toBe('0');
+ expect(node.defaultValue).toBe('0');
+
+ ReactDOM.render(, container);
+ expect(node.defaultValue).toBe('');
+ });
});