@samitouri / QOS-React / commits / 0bb0fe8d00

Fix controlled/uncontrolled validation for radio+checkbox inputs (#7003)

Jim committed Jun 9, 2016 at 07:58 UTC 0bb0fe8d0006c6f26e64723ffdbce3baf74c9ab1
2 files changed +46 -12
src/renderers/dom/client/wrappers/ReactDOMInput.js
+9 -12
@@ -48,6 +48,11 @@ function warnIfValueIsNull(props) {
48 }
49 }
50
51 +function isControlled(props) {
52 + var usesChecked = props.type === 'checkbox' || props.type === 'radio';
53 + return usesChecked ? props.checked !== undefined : props.value !== undefined;
54 +}
55 +
56 /**
57 * Implements an <input> host component that allows setting these optional
58 * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
@@ -156,7 +161,7 @@ var ReactDOMInput = {
161 };
162
163 if (__DEV__) {
159 - inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined;
164 + inst._wrapperState.controlled = isControlled(props);
165 }
166 },
167
@@ -166,14 +171,10 @@ var ReactDOMInput = {
171 if (__DEV__) {
172 warnIfValueIsNull(props);
173
169 - var defaultValue = props.defaultChecked || props.defaultValue;
170 - var controlled = props.checked !== undefined || props.value !== undefined;
174 + var controlled = isControlled(props);
175 var owner = inst._currentElement._owner;
176
173 - if (
174 - !inst._wrapperState.controlled &&
175 - controlled && !didWarnUncontrolledToControlled
176 - ) {
177 + if (!inst._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
178 warning(
179 false,
180 '%s is changing an uncontrolled input of type %s to be controlled. ' +
@@ -185,11 +186,7 @@ var ReactDOMInput = {
186 );
187 didWarnUncontrolledToControlled = true;
188 }
188 - if (
189 - inst._wrapperState.controlled &&
190 - (defaultValue || !controlled) &&
191 - !didWarnControlledToUncontrolled
192 - ) {
189 + if (inst._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
190 warning(
191 false,
192 '%s is changing a controlled input of type %s to be uncontrolled. ' +
src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js
+37
@@ -662,6 +662,43 @@ describe('ReactDOMInput', function() {
662 );
663 });
664
665 + it('should not warn if radio value changes but never becomes controlled', function() {
666 + var container = document.createElement('div');
667 + ReactDOM.render(<input type="radio" value="value" />, container);
668 + ReactDOM.render(<input type="radio" />, container);
669 + ReactDOM.render(<input type="radio" value="value" defaultChecked={true} />, container);
670 + ReactDOM.render(<input type="radio" value="value" onChange={() => null} />, container);
671 + ReactDOM.render(<input type="radio" />, container);
672 + expect(console.error.calls.count()).toBe(0);
673 + });
674 +
675 + it('should not warn if radio value changes but never becomes uncontrolled', function() {
676 + var container = document.createElement('div');
677 + ReactDOM.render(<input type="radio" checked={false} onChange={() => null} />, container);
678 + ReactDOM.render(
679 + <input
680 + type="radio"
681 + value="value"
682 + defaultChecked={true}
683 + checked={false}
684 + onChange={() => null}
685 + />, container);
686 + console.log(console.error.calls.argsFor(0)[0]);
687 + expect(console.error.calls.count()).toBe(0);
688 + });
689 +
690 + it('should warn if radio checked false changes to become uncontrolled', function() {
691 + var container = document.createElement('div');
692 + ReactDOM.render(<input type="radio" value="value" checked={false} onChange={() => null} />, container);
693 + ReactDOM.render(<input type="radio" value="value" />, container);
694 + expect(console.error.calls.argsFor(0)[0]).toContain(
695 + 'A component is changing a controlled input of type radio to be uncontrolled. ' +
696 + 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
697 + 'Decide between using a controlled or uncontrolled input ' +
698 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
699 + );
700 + });
701 +
702 it('sets type before value always', function() {
703 if (!ReactDOMFeatureFlags.useCreateElement) {
704 return;