Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jun 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions src/renderers/dom/client/wrappers/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ function warnIfValueIsNull(props) {
}
}

function isControlled(props) {
var usesChecked = props.type === 'checkbox' || props.type === 'radio';
return usesChecked ? props.checked !== undefined : props.value !== undefined;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spacing isn't FB style and this would be clearer anyway; can you change it?

function isControlled(props) {
  var usesChecked = props.type === 'checkbox' || props.type === 'radio';
  return usesChecked ? props.checked !== undefined : props.value !== undefined;
}


/**
* Implements an <input> host component that allows setting these optional
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
Expand Down Expand Up @@ -156,7 +161,7 @@ var ReactDOMInput = {
};

if (__DEV__) {
inst._wrapperState.controlled = props.checked !== undefined || props.value !== undefined;
inst._wrapperState.controlled = isControlled(props);
}
},

Expand All @@ -166,14 +171,10 @@ var ReactDOMInput = {
if (__DEV__) {
warnIfValueIsNull(props);

var defaultValue = props.defaultChecked || props.defaultValue;
var controlled = props.checked !== undefined || props.value !== undefined;
var controlled = isControlled(props);
var owner = inst._currentElement._owner;

if (
!inst._wrapperState.controlled &&
controlled && !didWarnUncontrolledToControlled
) {
if (!inst._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
warning(
false,
'%s is changing an uncontrolled input of type %s to be controlled. ' +
Expand All @@ -185,11 +186,7 @@ var ReactDOMInput = {
);
didWarnUncontrolledToControlled = true;
}
if (
inst._wrapperState.controlled &&
(defaultValue || !controlled) &&
!didWarnControlledToUncontrolled
) {
if (inst._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
warning(
false,
'%s is changing a controlled input of type %s to be uncontrolled. ' +
Expand Down
37 changes: 37 additions & 0 deletions src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,43 @@ describe('ReactDOMInput', function() {
);
});

it('should not warn if radio value changes but never becomes controlled', function() {
var container = document.createElement('div');
ReactDOM.render(<input type="radio" value="value" />, container);
ReactDOM.render(<input type="radio" />, container);
ReactDOM.render(<input type="radio" value="value" defaultChecked={true} />, container);
ReactDOM.render(<input type="radio" value="value" onChange={() => null} />, container);
ReactDOM.render(<input type="radio" />, container);
expect(console.error.calls.count()).toBe(0);
});

it('should not warn if radio value changes but never becomes uncontrolled', function() {
var container = document.createElement('div');
ReactDOM.render(<input type="radio" checked={false} onChange={() => null} />, container);
ReactDOM.render(
<input
type="radio"
value="value"
defaultChecked={true}
checked={false}
onChange={() => null}
/>, container);
console.log(console.error.calls.argsFor(0)[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave this console.log call here? 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pssh, thanks. #7006

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a lint rule for that.

expect(console.error.calls.count()).toBe(0);
});

it('should warn if radio checked false changes to become uncontrolled', function() {
var container = document.createElement('div');
ReactDOM.render(<input type="radio" value="value" checked={false} onChange={() => null} />, container);
ReactDOM.render(<input type="radio" value="value" />, container);
expect(console.error.calls.argsFor(0)[0]).toContain(
'A component is changing a controlled input of type radio to be uncontrolled. ' +
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
'Decide between using a controlled or uncontrolled input ' +
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components'
);
});

it('sets type before value always', function() {
if (!ReactDOMFeatureFlags.useCreateElement) {
return;
Expand Down