Skip to content

Commit b38b39a

Browse files
committed
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
1 parent 2f792d5 commit b38b39a

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)