mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Support two-way binding for checkboxes in LinkedValueMixin via 'checkedLink' property
This commit is contained in:
@@ -27,7 +27,7 @@ var hasReadOnlyValue = {
|
||||
'radio': true
|
||||
};
|
||||
|
||||
function _assertLink(input) {
|
||||
function _assertValueLink(input) {
|
||||
invariant(
|
||||
input.props.value == null && input.props.onChange == null,
|
||||
'Cannot provide a valueLink and a value or onChange event. If you want ' +
|
||||
@@ -35,6 +35,15 @@ function _assertLink(input) {
|
||||
);
|
||||
}
|
||||
|
||||
function _assertCheckedLink(input) {
|
||||
invariant(
|
||||
input.props.checked == null && input.props.onChange == null,
|
||||
'Cannot provide a checkedLink and a checked property or onChange event. ' +
|
||||
'If you want to use checked or onChange, you probably don\'t want to ' +
|
||||
'use checkedLink'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SyntheticEvent} e change event to handle
|
||||
*/
|
||||
@@ -43,6 +52,14 @@ function _handleLinkedValueChange(e) {
|
||||
this.props.valueLink.requestChange(e.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SyntheticEvent} e change event to handle
|
||||
*/
|
||||
function _handleLinkedCheckChange(e) {
|
||||
/*jshint validthis:true */
|
||||
this.props.checkedLink.requestChange(e.target.checked === 'true');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a linked `value` attribute for controlled forms. You should not use
|
||||
* this outside of the ReactDOM controlled form components.
|
||||
@@ -65,6 +82,21 @@ var LinkedValueUtils = {
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
checked: function(props, propName, componentName) {
|
||||
if (__DEV__) {
|
||||
if (props[propName] &&
|
||||
!props.onChange &&
|
||||
!props.readOnly &&
|
||||
!props.disabled) {
|
||||
console.warn(
|
||||
'You provided a `checked` prop to a form field without an ' +
|
||||
'`onChange` handler. This will render a read-only field. If ' +
|
||||
'the field should be mutable use `defaultChecked`. Otherwise, ' +
|
||||
'set either `onChange` or `readOnly`.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -75,20 +107,36 @@ var LinkedValueUtils = {
|
||||
*/
|
||||
getValue: function(input) {
|
||||
if (input.props.valueLink) {
|
||||
_assertLink(input);
|
||||
_assertValueLink(input);
|
||||
return input.props.valueLink.value;
|
||||
}
|
||||
return input.props.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ReactComponent} input Form component
|
||||
* @return {*} current checked status of the input either from checked prop
|
||||
* or link.
|
||||
*/
|
||||
getChecked: function(input) {
|
||||
if (input.props.checkedLink) {
|
||||
_assertCheckedLink(input);
|
||||
return input.props.checkedLink.value;
|
||||
}
|
||||
return input.props.checked;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ReactComponent} input Form component
|
||||
* @return {function} change callback either from onChange prop or link.
|
||||
*/
|
||||
getOnChange: function(input) {
|
||||
if (input.props.valueLink) {
|
||||
_assertLink(input);
|
||||
_assertValueLink(input);
|
||||
return _handleLinkedValueChange;
|
||||
} else if (input.props.checkedLink) {
|
||||
_assertCheckedLink(input);
|
||||
return _handleLinkedCheckChange;
|
||||
}
|
||||
return input.props.onChange;
|
||||
}
|
||||
|
||||
@@ -73,12 +73,13 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
|
||||
|
||||
props.defaultChecked = null;
|
||||
props.defaultValue = null;
|
||||
props.checked =
|
||||
this.props.checked != null ? this.props.checked : this.state.checked;
|
||||
|
||||
var value = LinkedValueUtils.getValue(this);
|
||||
props.value = value != null ? value : this.state.value;
|
||||
|
||||
var checked = LinkedValueUtils.getChecked(this);
|
||||
props.checked = checked != null ? checked : this.state.checked;
|
||||
|
||||
props.onChange = this._handleChange;
|
||||
|
||||
return input(props, this.props.children);
|
||||
|
||||
@@ -252,4 +252,77 @@ describe('ReactDOMInput', function() {
|
||||
expect(React.renderComponent.bind(React, instance, node)).toThrow();
|
||||
|
||||
});
|
||||
|
||||
it('should support checkedLink', function() {
|
||||
var container = document.createElement('div');
|
||||
var link = new ReactLink(true, mocks.getMockFunction());
|
||||
var instance = <input type="checked" checkedLink={link} />;
|
||||
|
||||
React.renderComponent(instance, container);
|
||||
|
||||
expect(instance.getDOMNode().checked).toBe(true);
|
||||
expect(link.value).toBe(true);
|
||||
expect(link.requestChange.mock.calls.length).toBe(0);
|
||||
|
||||
instance.getDOMNode().checked = false;
|
||||
ReactTestUtils.Simulate.input(instance.getDOMNode());
|
||||
|
||||
expect(link.requestChange.mock.calls.length).toBe(1);
|
||||
expect(link.requestChange.mock.calls[0][0]).toEqual(false);
|
||||
});
|
||||
|
||||
it('should warn with checked and no onChange handler', function() {
|
||||
var oldWarn = console.warn;
|
||||
try {
|
||||
console.warn = mocks.getMockFunction();
|
||||
|
||||
var node = document.createElement('div');
|
||||
var link = new ReactLink(true, mocks.getMockFunction());
|
||||
React.renderComponent(<input type="checkbox" checkedLink={link} />, node);
|
||||
expect(console.warn.mock.calls.length).toBe(0);
|
||||
|
||||
React.renderComponent(
|
||||
<input type="checkbox" checked="false" onChange={mocks.getMockFunction()} />,
|
||||
node
|
||||
);
|
||||
expect(console.warn.mock.calls.length).toBe(0);
|
||||
|
||||
React.renderComponent(
|
||||
<input type="checkbox" checked="false" readOnly={true} />,
|
||||
node
|
||||
);
|
||||
expect(console.warn.mock.calls.length).toBe(0);
|
||||
|
||||
React.renderComponent(<input type="checkbox" checked="false" />, node);
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
|
||||
React.renderComponent(
|
||||
<input type="checkbox" checked="false" readOnly={false} />,
|
||||
node
|
||||
);
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
} finally {
|
||||
console.warn = oldWarn;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw if both checked and checkedLink are provided', function() {
|
||||
// Silences console.error messages
|
||||
// ReactErrorUtils.guard is applied to all methods of a React component
|
||||
// and calls console.error in __DEV__ (true for test environment)
|
||||
spyOn(console, 'error');
|
||||
|
||||
var node = document.createElement('div');
|
||||
var link = new ReactLink(true, mocks.getMockFunction());
|
||||
var instance = <input type="checkbox" checkedLink={link} />;
|
||||
|
||||
expect(React.renderComponent.bind(React, instance, node)).not.toThrow();
|
||||
|
||||
instance = <input type="checkbox" checkedLink={link} checked="false" />;
|
||||
expect(React.renderComponent.bind(React, instance, node)).toThrow();
|
||||
|
||||
instance = <input type="checkbox" checkedLink={link} onChange={emptyFunction} />;
|
||||
expect(React.renderComponent.bind(React, instance, node)).toThrow();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user