@samitouri / QOS-React-2 / commits / b38b39a696

warn when an input switches between controlled and uncontrolled

added controlled key to DEV warn for checkbox inputs warn for radio inputs compute controlled instead of value displaying owner name in warning displaying input type in warnings

Julio Lopez committed Jan 16, 2016 at 00:59 UTC b38b39a696ea279f08c6fd35720caf96d1c78c20
2 files changed +169
src/renderers/dom/client/wrappers/ReactDOMInput.js
+43
@@ -25,6 +25,8 @@ var didWarnCheckedLink = false;
25 var didWarnValueNull = false;
26 var didWarnValueDefaultValue = false;
27 var didWarnCheckedDefaultChecked = false;
28 +var didWarnControlledToUncontrolled = false;
29 +var didWarnUncontrolledToControlled = false;
30
31 function forceUpdateIfMounted() {
32 if (this._rootNodeID) {
@@ -144,6 +146,10 @@ var ReactDOMInput = {
146 listeners: null,
147 onChange: _handleChange.bind(inst),
148 };
149 +
150 + if (__DEV__) {
151 + inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined;
152 + }
153 },
154
155 updateWrapper: function(inst) {
@@ -151,6 +157,43 @@ var ReactDOMInput = {
157
158 if (__DEV__) {
159 warnIfValueIsNull(props);
160 +
161 + var initialValue = inst._wrapperState.initialChecked || inst._wrapperState.initialValue;
162 + var defaultValue = props.defaultChecked || props.defaultValue;
163 + var controlled = props.checked !== undefined || props.value !== undefined;
164 + var owner = inst._currentElement._owner;
165 +
166 + if (
167 + (initialValue || !inst._wrapperState.controlled) &&
168 + controlled && !didWarnUncontrolledToControlled
169 + ) {
170 + warning(
171 + false,
172 + '%s is changing a uncontrolled input of type %s to be controlled. ' +
173 + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' +
174 + 'Decide between using a controlled or uncontrolled input ' +
175 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
176 + owner && owner.getName() || 'A component',
177 + props.type
178 + );
179 + didWarnUncontrolledToControlled = true;
180 + }
181 + if (
182 + inst._wrapperState.controlled &&
183 + (defaultValue || !controlled) &&
184 + !didWarnControlledToUncontrolled
185 + ) {
186 + warning(
187 + false,
188 + '%s is changing a controlled input of type %s to be uncontrolled. ' +
189 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
190 + 'Decide between using a controlled or uncontrolled input ' +
191 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
192 + owner && owner.getName() || 'A component',
193 + props.type
194 + );
195 + didWarnControlledToUncontrolled = true;
196 + }
197 }
198
199 // TODO: Shouldn't this be getChecked(props)?
src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js
+126
@@ -451,6 +451,132 @@ describe('ReactDOMInput', function() {
451 expect(console.error.argsForCall.length).toBe(1);
452 });
453
454 + it('should warn if controlled input switches to uncontrolled', function() {
455 + var stub = <input type="text" value="controlled" onChange={emptyFunction} />;
456 + var container = document.createElement('div');
457 + ReactDOM.render(stub, container);
458 + ReactDOM.render(<input type="text" />, container);
459 + expect(console.error.argsForCall.length).toBe(1);
460 + expect(console.error.argsForCall[0][0]).toContain(
461 + 'A component is changing a controlled input of type text to be uncontrolled. ' +
462 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
463 + 'Decide between using a controlled or uncontrolled input ' +
464 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
465 + );
466 + });
467 +
468 + it('should warn if controlled input switches to uncontrolled with defaultValue', function() {
469 + var stub = <input type="text" value="controlled" onChange={emptyFunction} />;
470 + var container = document.createElement('div');
471 + ReactDOM.render(stub, container);
472 + ReactDOM.render(<input type="text" defaultValue="uncontrolled" />, container);
473 + expect(console.error.argsForCall.length).toBe(1);
474 + expect(console.error.argsForCall[0][0]).toContain(
475 + 'A component is changing a controlled input of type text to be uncontrolled. ' +
476 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
477 + 'Decide between using a controlled or uncontrolled input ' +
478 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
479 + );
480 + });
481 +
482 + it('should warn if uncontrolled input switches to controlled', function() {
483 + var stub = <input type="text" />;
484 + var container = document.createElement('div');
485 + ReactDOM.render(stub, container);
486 + ReactDOM.render(<input type="text" value="controlled" />, container);
487 + expect(console.error.argsForCall.length).toBe(1);
488 + expect(console.error.argsForCall[0][0]).toContain(
489 + 'A component is changing a uncontrolled input of type text to be controlled. ' +
490 + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' +
491 + 'Decide between using a controlled or uncontrolled input ' +
492 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
493 + );
494 + });
495 +
496 + it('should warn if controlled checkbox switches to uncontrolled', function() {
497 + var stub = <input type="checkbox" checked={true} onChange={emptyFunction} />;
498 + var container = document.createElement('div');
499 + ReactDOM.render(stub, container);
500 + ReactDOM.render(<input type="checkbox" />, container);
501 + expect(console.error.argsForCall.length).toBe(1);
502 + expect(console.error.argsForCall[0][0]).toContain(
503 + 'A component is changing a controlled input of type checkbox to be uncontrolled. ' +
504 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
505 + 'Decide between using a controlled or uncontrolled input ' +
506 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
507 + );
508 + });
509 +
510 + it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', function() {
511 + var stub = <input type="checkbox" checked={true} onChange={emptyFunction} />;
512 + var container = document.createElement('div');
513 + ReactDOM.render(stub, container);
514 + ReactDOM.render(<input type="checkbox" defaultChecked={true} />, container);
515 + expect(console.error.argsForCall.length).toBe(1);
516 + expect(console.error.argsForCall[0][0]).toContain(
517 + 'A component is changing a controlled input of type checkbox to be uncontrolled. ' +
518 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
519 + 'Decide between using a controlled or uncontrolled input ' +
520 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
521 + );
522 + });
523 +
524 + it('should warn if uncontrolled checkbox switches to controlled', function() {
525 + var stub = <input type="checkbox" />;
526 + var container = document.createElement('div');
527 + ReactDOM.render(stub, container);
528 + ReactDOM.render(<input type="checkbox" checked={true} />, container);
529 + expect(console.error.argsForCall.length).toBe(1);
530 + expect(console.error.argsForCall[0][0]).toContain(
531 + 'A component is changing a uncontrolled input of type checkbox to be controlled. ' +
532 + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' +
533 + 'Decide between using a controlled or uncontrolled input ' +
534 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
535 + );
536 + });
537 +
538 + it('should warn if controlled radio switches to uncontrolled', function() {
539 + var stub = <input type="radio" checked={true} onChange={emptyFunction} />;
540 + var container = document.createElement('div');
541 + ReactDOM.render(stub, container);
542 + ReactDOM.render(<input type="radio" />, container);
543 + expect(console.error.argsForCall.length).toBe(1);
544 + expect(console.error.argsForCall[0][0]).toContain(
545 + 'A component is changing a controlled input of type radio to be uncontrolled. ' +
546 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
547 + 'Decide between using a controlled or uncontrolled input ' +
548 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
549 + );
550 + });
551 +
552 + it('should warn if controlled radio switches to uncontrolled with defaultChecked', function() {
553 + var stub = <input type="radio" checked={true} onChange={emptyFunction} />;
554 + var container = document.createElement('div');
555 + ReactDOM.render(stub, container);
556 + ReactDOM.render(<input type="radio" defaultChecked={true} />, container);
557 + expect(console.error.argsForCall.length).toBe(1);
558 + expect(console.error.argsForCall[0][0]).toContain(
559 + 'A component is changing a controlled input of type radio to be uncontrolled. ' +
560 + 'Input elements should not switch from controlled to uncontrolled (or viceversa). ' +
561 + 'Decide between using a controlled or uncontrolled input ' +
562 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
563 + );
564 + });
565 +
566 + it('should warn if uncontrolled radio switches to controlled', function() {
567 + var stub = <input type="radio" />;
568 + var container = document.createElement('div');
569 + ReactDOM.render(stub, container);
570 + ReactDOM.render(<input type="radio" checked={true} />, container);
571 + expect(console.error.argsForCall.length).toBe(1);
572 + expect(console.error.argsForCall[0][0]).toContain(
573 + 'A component is changing a uncontrolled input of type radio to be controlled. ' +
574 + 'Input elements should not switch from uncontrolled to controlled (or viceversa). ' +
575 + 'Decide between using a controlled or uncontrolled input ' +
576 + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
577 + );
578 + });
579 +
580 it('sets type before value always', function() {
581 var log = [];
582 var originalCreateElement = document.createElement;