From f2b62e9d903b2144cbd3b979e30c316ee287f5a1 Mon Sep 17 00:00:00 2001 From: Michael McDermott Date: Mon, 11 Jan 2016 18:09:37 -0500 Subject: [PATCH] Warn when both value and defaultValue or both check and defaultChecked props are specified on input, textarea, or select elements --- .../dom/client/wrappers/ReactDOMInput.js | 24 +++++++++++++ .../dom/client/wrappers/ReactDOMSelect.js | 13 +++++++ .../dom/client/wrappers/ReactDOMTextarea.js | 11 ++++++ .../wrappers/__tests__/ReactDOMInput-test.js | 34 +++++++++++++++++++ .../wrappers/__tests__/ReactDOMSelect-test.js | 26 ++++++++++++++ .../__tests__/ReactDOMTextarea-test.js | 18 ++++++++++ 6 files changed, 126 insertions(+) diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index 69d797b7cdf3f..4728bddbe8929 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -25,6 +25,8 @@ var instancesByReactID = {}; var didWarnValueLink = false; var didWarnCheckedLink = false; var didWarnValueNull = false; +var didWarnValueDefaultValue = false; +var didWarnCheckedDefaultChecked = false; function forceUpdateIfMounted() { if (this._rootNodeID) { @@ -104,6 +106,28 @@ var ReactDOMInput = { ); didWarnCheckedLink = true; } + if (props.checked !== undefined && props.defaultChecked !== undefined && + !didWarnCheckedDefaultChecked) { + warning( + false, + 'Input elements must be either controlled or uncontrolled (specify either the ' + + 'checked prop, or the defaultChecked prop, but not both). Decide between using a ' + + 'controlled or uncontrolled input and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + didWarnCheckedDefaultChecked = true; + } + if (props.value !== undefined && props.defaultValue !== undefined && + !didWarnValueDefaultValue) { + warning( + false, + 'Input elements must be either controlled or uncontrolled (specify either the value ' + + 'prop, or the defaultValue prop, but not both). Decide between using a controlled ' + + 'or uncontrolled input and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + didWarnValueDefaultValue = true; + } warnIfValueIsNull(props); } diff --git a/src/renderers/dom/client/wrappers/ReactDOMSelect.js b/src/renderers/dom/client/wrappers/ReactDOMSelect.js index 1bfa652af80a5..1ef4ccd5b739d 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMSelect.js +++ b/src/renderers/dom/client/wrappers/ReactDOMSelect.js @@ -20,6 +20,7 @@ var warning = require('warning'); var didWarnValueLink = false; var didWarnValueNull = false; +var didWarnValueDefaultValue = false; function updateOptionsIfPendingUpdateAndMounted() { if (this._rootNodeID && this._wrapperState.pendingUpdate) { @@ -178,6 +179,18 @@ var ReactDOMSelect = { onChange: _handleChange.bind(inst), wasMultiple: Boolean(props.multiple), }; + + if (props.value !== undefined && props.defaultValue !== undefined && + !didWarnValueDefaultValue) { + warning( + false, + 'Select elements must be either controlled or uncontrolled (specify either the ' + + 'value prop, or the defaultValue prop, but not both). Decide between using a ' + + 'controlled or uncontrolled select element and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + didWarnValueDefaultValue = true; + } }, getSelectValueContext: function(inst) { diff --git a/src/renderers/dom/client/wrappers/ReactDOMTextarea.js b/src/renderers/dom/client/wrappers/ReactDOMTextarea.js index f3e9285f9300d..5ed80996cf428 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMTextarea.js +++ b/src/renderers/dom/client/wrappers/ReactDOMTextarea.js @@ -22,6 +22,7 @@ var warning = require('warning'); var didWarnValueLink = false; var didWarnValueNull = false; +var didWarnValDefaultVal = false; function forceUpdateIfMounted() { if (this._rootNodeID) { @@ -91,6 +92,16 @@ var ReactDOMTextarea = { ); didWarnValueLink = true; } + if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValDefaultVal) { + warning( + false, + 'Textarea elements must be either controlled or uncontrolled (specify either the value ' + + 'prop, or the defaultValue prop, but not both). Decide between using a controlled or ' + + 'uncontrolled input and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + didWarnValDefaultVal = true; + } warnIfValueIsNull(props); } diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index f3fd0a2371399..d9463b70cfac1 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -415,6 +415,40 @@ describe('ReactDOMInput', function() { expect(console.error.argsForCall.length).toBe(1); }); + it('should throw warning message if checked and defaultChecked props are specified', function() { + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall[0][0]).toContain( + 'Input elements must be either controlled or uncontrolled (specify either the ' + + 'checked prop, or the defaultChecked prop, but not both). Decide between using a ' + + 'controlled or uncontrolled input and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall.length).toBe(1); + }); + + it('should throw warning message if value and defaultValue props are specified', function() { + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall[0][0]).toContain( + 'Input elements must be either controlled or uncontrolled (specify either the value ' + + 'prop, or the defaultValue prop, but not both). Decide between using a controlled or ' + + 'uncontrolled input and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall.length).toBe(1); + }); + it('sets type before value always', function() { var log = []; var originalCreateElement = document.createElement; diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js index f132b81846255..6de9e25f9745b 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js @@ -490,4 +490,30 @@ describe('ReactDOMSelect', function() { expect(node.value).toBe('giraffe'); }); + + it('should throw warning message if value and defaultValue props are specified', function() { + spyOn(console, 'error'); + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall[0][0]).toContain( + 'Select elements must be either controlled or uncontrolled (specify either the ' + + 'value prop, or the defaultValue prop, but not both). Decide between using a ' + + 'controlled or uncontrolled select element and remove one of these props. More info: ' + + 'https://fb.me/react-controlled-components' + ); + + ReactTestUtils.renderIntoDocument( + + ); + expect(console.error.argsForCall.length).toBe(1); + }); }); diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMTextarea-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMTextarea-test.js index 226e556da8f84..b6b9a17a67725 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMTextarea-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMTextarea-test.js @@ -278,4 +278,22 @@ describe('ReactDOMTextarea', function() { ReactTestUtils.renderIntoDocument(