Skip to content

Commit 31c0b79

Browse files
jimfbjim
authored and
jim
committed
Fix controlled/uncontrolled validation for radio+checkbox inputs
1 parent c47830d commit 31c0b79

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

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

+9-11
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ function warnIfValueIsNull(props) {
4848
}
4949
}
5050

51+
function isControlled(props) {
52+
return ((props.type === 'checkbox' || props.type === 'radio') && props.checked !== undefined) ||
53+
(props.type !== 'checkbox' && props.type !== 'radio' && props.value !== undefined);
54+
}
55+
5156
/**
5257
* Implements an <input> host component that allows setting these optional
5358
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
@@ -156,7 +161,7 @@ var ReactDOMInput = {
156161
};
157162

158163
if (__DEV__) {
159-
inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined;
164+
inst._wrapperState.controlled = isControlled(props);
160165
}
161166
},
162167

@@ -167,13 +172,10 @@ var ReactDOMInput = {
167172
warnIfValueIsNull(props);
168173

169174
var defaultValue = props.defaultChecked || props.defaultValue;
170-
var controlled = props.checked !== undefined || props.value !== undefined;
175+
var controlled = isControlled(props);
171176
var owner = inst._currentElement._owner;
172177

173-
if (
174-
!inst._wrapperState.controlled &&
175-
controlled && !didWarnUncontrolledToControlled
176-
) {
178+
if (!inst._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
177179
warning(
178180
false,
179181
'%s is changing an uncontrolled input of type %s to be controlled. ' +
@@ -185,11 +187,7 @@ var ReactDOMInput = {
185187
);
186188
didWarnUncontrolledToControlled = true;
187189
}
188-
if (
189-
inst._wrapperState.controlled &&
190-
(defaultValue || !controlled) &&
191-
!didWarnControlledToUncontrolled
192-
) {
190+
if (inst._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
193191
warning(
194192
false,
195193
'%s is changing a controlled input of type %s to be uncontrolled. ' +

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

+37
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,43 @@ describe('ReactDOMInput', function() {
662662
);
663663
});
664664

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+
665702
it('sets type before value always', function() {
666703
if (!ReactDOMFeatureFlags.useCreateElement) {
667704
return;

0 commit comments

Comments
 (0)