diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index 881e8c146b44c..e52c199b24a15 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -25,6 +25,8 @@ var didWarnCheckedLink = false; var didWarnValueNull = false; var didWarnValueDefaultValue = false; var didWarnCheckedDefaultChecked = false; +var didWarnControlledToUncontrolled = false; +var didWarnUncontrolledToControlled = false; function forceUpdateIfMounted() { if (this._rootNodeID) { @@ -144,6 +146,10 @@ var ReactDOMInput = { listeners: null, onChange: _handleChange.bind(inst), }; + + if (__DEV__) { + inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined; + } }, updateWrapper: function(inst) { @@ -151,6 +157,43 @@ var ReactDOMInput = { if (__DEV__) { warnIfValueIsNull(props); + + var initialValue = inst._wrapperState.initialChecked || inst._wrapperState.initialValue; + var defaultValue = props.defaultChecked || props.defaultValue; + var controlled = props.checked !== undefined || props.value !== undefined; + var owner = inst._currentElement._owner; + + if ( + (initialValue || !inst._wrapperState.controlled) && + controlled && !didWarnUncontrolledToControlled + ) { + warning( + false, + '%s is changing a uncontrolled input of type %s to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + owner && owner.getName() || 'A component', + props.type + ); + didWarnUncontrolledToControlled = true; + } + if ( + inst._wrapperState.controlled && + (defaultValue || !controlled) && + !didWarnControlledToUncontrolled + ) { + warning( + false, + '%s is changing a controlled input of type %s to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', + owner && owner.getName() || 'A component', + props.type + ); + didWarnControlledToUncontrolled = true; + } } // TODO: Shouldn't this be getChecked(props)? diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index 196ff290fafe0..406b25fd00834 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -451,6 +451,132 @@ describe('ReactDOMInput', function() { expect(console.error.argsForCall.length).toBe(1); }); + it('should warn if controlled input switches to uncontrolled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type text to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if controlled input switches to uncontrolled with defaultValue', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type text to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if uncontrolled input switches to controlled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a uncontrolled input of type text to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if controlled checkbox switches to uncontrolled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type checkbox to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type checkbox to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if uncontrolled checkbox switches to controlled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a uncontrolled input of type checkbox to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if controlled radio switches to uncontrolled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if controlled radio switches to uncontrolled with defaultChecked', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a controlled input of type radio to be uncontrolled. ' + + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + + it('should warn if uncontrolled radio switches to controlled', function() { + var stub = ; + var container = document.createElement('div'); + ReactDOM.render(stub, container); + ReactDOM.render(, container); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'A component is changing a uncontrolled input of type radio to be controlled. ' + + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' + + 'Decide between using a controlled or uncontrolled input ' + + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' + ); + }); + it('sets type before value always', function() { var log = []; var originalCreateElement = document.createElement;