Skip to content

Commit d684b15

Browse files
committed
Merge pull request facebook#5864 from TheBlasfem/master
Warn when an input switches between controlled and uncontrolled
2 parents b3eaab9 + b38b39a commit d684b15

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

src/renderers/dom/client/wrappers/ReactDOMInput.js

+43
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ var didWarnCheckedLink = false;
2525
var didWarnValueNull = false;
2626
var didWarnValueDefaultValue = false;
2727
var didWarnCheckedDefaultChecked = false;
28+
var didWarnControlledToUncontrolled = false;
29+
var didWarnUncontrolledToControlled = false;
2830

2931
function forceUpdateIfMounted() {
3032
if (this._rootNodeID) {
@@ -144,13 +146,54 @@ var ReactDOMInput = {
144146
listeners: null,
145147
onChange: _handleChange.bind(inst),
146148
};
149+
150+
if (__DEV__) {
151+
inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined;
152+
}
147153
},
148154

149155
updateWrapper: function(inst) {
150156
var props = inst._currentElement.props;
151157

152158
if (__DEV__) {
153159
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+
}
154197
}
155198

156199
// TODO: Shouldn't this be getChecked(props)?

src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js

+126
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,132 @@ describe('ReactDOMInput', function() {
451451
expect(console.error.argsForCall.length).toBe(1);
452452
});
453453

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+
454580
it('sets type before value always', function() {
455581
var log = [];
456582
var originalCreateElement = document.createElement;

0 commit comments

Comments
 (0)