diff --git a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
index 1fba16247e2bd..56319c95d76a7 100644
--- a/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
+++ b/packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
@@ -79,7 +79,7 @@ describe('DOMPropertyOperations', () => {
it('should not remove empty attributes for special properties', () => {
const container = document.createElement('div');
- ReactDOM.render(, container);
+ ReactDOM.render( {}} />, container);
expect(container.firstChild.getAttribute('value')).toBe('');
expect(container.firstChild.value).toBe('');
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
index 480287cfc0088..5afa9dcf7aaf4 100644
--- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js
@@ -47,6 +47,71 @@ describe('ReactDOMInput', () => {
document.body.removeChild(container);
});
+ it('should warn for controlled value of 0 with missing onChange', () => {
+ expect(() => {
+ ReactDOM.render(, container);
+ }).toWarnDev(
+ 'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
+ );
+ });
+
+ it('should warn for controlled value of "" with missing onChange', () => {
+ expect(() => {
+ ReactDOM.render(, container);
+ }).toWarnDev(
+ 'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
+ );
+ });
+
+ it('should warn for controlled value of "0" with missing onChange', () => {
+ expect(() => {
+ ReactDOM.render(, container);
+ }).toWarnDev(
+ 'Failed prop type: You provided a `value` prop to a form field without an `onChange` handler.',
+ );
+ });
+
+ it('should warn for controlled value of false with missing onChange', () => {
+ expect(() =>
+ ReactDOM.render(, container),
+ ).toWarnDev(
+ 'Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler.',
+ );
+ });
+
+ it('should warn with checked and no onChange handler with readOnly specified', () => {
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.unmountComponentAtNode(container);
+
+ expect(() =>
+ ReactDOM.render(
+ ,
+ container,
+ ),
+ ).toWarnDev(
+ 'Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. ' +
+ 'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
+ 'Otherwise, set either `onChange` or `readOnly`.',
+ );
+ });
+
+ it('should not warn about missing onChange in uncontrolled inputs', () => {
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ });
+
it('should properly control a value even if no event listener exists', () => {
let node;
@@ -452,7 +517,7 @@ describe('ReactDOMInput', () => {
});
it('should display `value` of number 0', () => {
- const stub = ;
+ const stub = ;
const node = ReactDOM.render(stub, container);
expect(node.value).toBe('0');
@@ -590,8 +655,14 @@ describe('ReactDOMInput', () => {
});
it('should properly transition from an empty value to 0', function() {
- ReactDOM.render(, container);
- ReactDOM.render(, container);
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.render(
+ ,
+ container,
+ );
const node = container.firstChild;
@@ -600,8 +671,14 @@ describe('ReactDOMInput', () => {
});
it('should properly transition from 0 to an empty value', function() {
- ReactDOM.render(, container);
- ReactDOM.render(, container);
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.render(
+ ,
+ container,
+ );
const node = container.firstChild;
@@ -610,8 +687,14 @@ describe('ReactDOMInput', () => {
});
it('should properly transition a text input from 0 to an empty 0.0', function() {
- ReactDOM.render(, container);
- ReactDOM.render(, container);
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.render(
+ ,
+ container,
+ );
const node = container.firstChild;
@@ -620,8 +703,14 @@ describe('ReactDOMInput', () => {
});
it('should properly transition a number input from "" to 0', function() {
- ReactDOM.render(, container);
- ReactDOM.render(, container);
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.render(
+ ,
+ container,
+ );
const node = container.firstChild;
@@ -630,8 +719,14 @@ describe('ReactDOMInput', () => {
});
it('should properly transition a number input from "" to "0"', function() {
- ReactDOM.render(, container);
- ReactDOM.render(, container);
+ ReactDOM.render(
+ ,
+ container,
+ );
+ ReactDOM.render(
+ ,
+ container,
+ );
const node = container.firstChild;
@@ -874,25 +969,6 @@ describe('ReactDOMInput', () => {
dispatchEventOnNode(node, 'input');
});
- it('should warn with checked and no onChange handler with readOnly specified', () => {
- ReactDOM.render(
- ,
- container,
- );
- ReactDOM.unmountComponentAtNode(container);
-
- expect(() =>
- ReactDOM.render(
- ,
- container,
- ),
- ).toWarnDev(
- 'Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. ' +
- 'This will render a read-only field. If the field should be mutable use `defaultChecked`. ' +
- 'Otherwise, set either `onChange` or `readOnly`.',
- );
- });
-
it('should update defaultValue to empty string', () => {
ReactDOM.render(, container);
ReactDOM.render(, container);
diff --git a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
index 5548c039316a8..e94cc7895cbbb 100644
--- a/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMSelect-test.js
@@ -654,6 +654,13 @@ describe('ReactDOMSelect', () => {
);
});
+ it('should not warn about missing onChange in uncontrolled textareas', () => {
+ const container = document.createElement('div');
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ });
+
it('should be able to safely remove select onChange', () => {
function changeView() {
ReactDOM.unmountComponentAtNode(container);
diff --git a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
index 73522ace5d4cc..fa5d717974aae 100644
--- a/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
@@ -97,7 +97,7 @@ describe('ReactDOMTextarea', () => {
});
it('should display `value` of number 0', () => {
- const stub = ;
+ const stub = ;
const node = renderTextarea(stub);
expect(node.value).toBe('0');
@@ -416,4 +416,11 @@ describe('ReactDOMTextarea', () => {
,
);
});
+
+ it('should not warn about missing onChange in uncontrolled textareas', () => {
+ const container = document.createElement('div');
+ ReactDOM.render(, container);
+ ReactDOM.unmountComponentAtNode(container);
+ ReactDOM.render(, container);
+ });
});
diff --git a/packages/react-dom/src/shared/ReactControlledValuePropTypes.js b/packages/react-dom/src/shared/ReactControlledValuePropTypes.js
index 0f4fe58ee2653..65e16ffbe2eaa 100644
--- a/packages/react-dom/src/shared/ReactControlledValuePropTypes.js
+++ b/packages/react-dom/src/shared/ReactControlledValuePropTypes.js
@@ -30,11 +30,12 @@ if (__DEV__) {
const propTypes = {
value: function(props, propName, componentName) {
if (
- !props[propName] ||
+ !(propName in props) ||
hasReadOnlyValue[props.type] ||
props.onChange ||
props.readOnly ||
- props.disabled
+ props.disabled ||
+ props[propName] == null
) {
return null;
}
@@ -47,10 +48,11 @@ if (__DEV__) {
},
checked: function(props, propName, componentName) {
if (
- !props[propName] ||
+ !(propName in props) ||
props.onChange ||
props.readOnly ||
- props.disabled
+ props.disabled ||
+ props[propName] == null
) {
return null;
}