Support two-way binding for checkboxes in LinkedValueMixin via 'checkedLink' property
Johannes Baiter committed
Jan 13, 2014 at 22:26 UTC
7b047111a0584d5a730764ff33fa37a74fc7b5fc
3 files changed
+127
-5
src/dom/components/LinkedValueUtils.js
+51
-3
@@ -27,7 +27,7 @@ var hasReadOnlyValue = {
27
'radio': true
28
};
29
30
-function _assertLink(input) {
30
+function _assertValueLink(input) {
31
invariant(
32
input.props.value == null && input.props.onChange == null,
33
'Cannot provide a valueLink and a value or onChange event. If you want ' +
@@ -35,6 +35,15 @@ function _assertLink(input) {
35
);
36
}
37
38
+function _assertCheckedLink(input) {
39
+ invariant(
40
+ input.props.checked == null && input.props.onChange == null,
41
+ 'Cannot provide a checkedLink and a checked property or onChange event. ' +
42
+ 'If you want to use checked or onChange, you probably don\'t want to ' +
43
+ 'use checkedLink'
44
+ );
45
+}
46
+
47
/**
48
* @param {SyntheticEvent} e change event to handle
49
*/
@@ -43,6 +52,14 @@ function _handleLinkedValueChange(e) {
52
this.props.valueLink.requestChange(e.target.value);
53
}
54
55
+/**
56
+ * @param {SyntheticEvent} e change event to handle
57
+ */
58
+function _handleLinkedCheckChange(e) {
59
+ /*jshint validthis:true */
60
+ this.props.checkedLink.requestChange(e.target.checked === 'true');
61
+}
62
+
63
/**
64
* Provide a linked `value` attribute for controlled forms. You should not use
65
* this outside of the ReactDOM controlled form components.
@@ -65,6 +82,21 @@ var LinkedValueUtils = {
82
);
83
}
84
}
85
+ },
86
+ checked: function(props, propName, componentName) {
87
+ if (__DEV__) {
88
+ if (props[propName] &&
89
+ !props.onChange &&
90
+ !props.readOnly &&
91
+ !props.disabled) {
92
+ console.warn(
93
+ 'You provided a `checked` prop to a form field without an ' +
94
+ '`onChange` handler. This will render a read-only field. If ' +
95
+ 'the field should be mutable use `defaultChecked`. Otherwise, ' +
96
+ 'set either `onChange` or `readOnly`.'
97
+ );
98
+ }
99
+ }
100
}
101
}
102
},
@@ -75,20 +107,36 @@ var LinkedValueUtils = {
107
*/
108
getValue: function(input) {
109
if (input.props.valueLink) {
78
- _assertLink(input);
110
+ _assertValueLink(input);
111
return input.props.valueLink.value;
112
}
113
return input.props.value;
114
},
115
116
+ /**
117
+ * @param {ReactComponent} input Form component
118
+ * @return {*} current checked status of the input either from checked prop
119
+ * or link.
120
+ */
121
+ getChecked: function(input) {
122
+ if (input.props.checkedLink) {
123
+ _assertCheckedLink(input);
124
+ return input.props.checkedLink.value;
125
+ }
126
+ return input.props.checked;
127
+ },
128
+
129
/**
130
* @param {ReactComponent} input Form component
131
* @return {function} change callback either from onChange prop or link.
132
*/
133
getOnChange: function(input) {
134
if (input.props.valueLink) {
90
- _assertLink(input);
135
+ _assertValueLink(input);
136
return _handleLinkedValueChange;
137
+ } else if (input.props.checkedLink) {
138
+ _assertCheckedLink(input);
139
+ return _handleLinkedCheckChange;
140
}
141
return input.props.onChange;
142
}
src/dom/components/ReactDOMInput.js
+3
-2
@@ -73,12 +73,13 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
73
74
props.defaultChecked = null;
75
props.defaultValue = null;
76
- props.checked =
77
- this.props.checked != null ? this.props.checked : this.state.checked;
76
77
var value = LinkedValueUtils.getValue(this);
78
props.value = value != null ? value : this.state.value;
79
80
+ var checked = LinkedValueUtils.getChecked(this);
81
+ props.checked = checked != null ? checked : this.state.checked;
82
+
83
props.onChange = this._handleChange;
84
85
return input(props, this.props.children);
src/dom/components/__tests__/ReactDOMInput-test.js
+73
@@ -252,4 +252,77 @@ describe('ReactDOMInput', function() {
252
expect(React.renderComponent.bind(React, instance, node)).toThrow();
253
254
});
255
+
256
+ it('should support checkedLink', function() {
257
+ var container = document.createElement('div');
258
+ var link = new ReactLink(true, mocks.getMockFunction());
259
+ var instance = <input type="checked" checkedLink={link} />;
260
+
261
+ React.renderComponent(instance, container);
262
+
263
+ expect(instance.getDOMNode().checked).toBe(true);
264
+ expect(link.value).toBe(true);
265
+ expect(link.requestChange.mock.calls.length).toBe(0);
266
+
267
+ instance.getDOMNode().checked = false;
268
+ ReactTestUtils.Simulate.input(instance.getDOMNode());
269
+
270
+ expect(link.requestChange.mock.calls.length).toBe(1);
271
+ expect(link.requestChange.mock.calls[0][0]).toEqual(false);
272
+ });
273
+
274
+ it('should warn with checked and no onChange handler', function() {
275
+ var oldWarn = console.warn;
276
+ try {
277
+ console.warn = mocks.getMockFunction();
278
+
279
+ var node = document.createElement('div');
280
+ var link = new ReactLink(true, mocks.getMockFunction());
281
+ React.renderComponent(<input type="checkbox" checkedLink={link} />, node);
282
+ expect(console.warn.mock.calls.length).toBe(0);
283
+
284
+ React.renderComponent(
285
+ <input type="checkbox" checked="false" onChange={mocks.getMockFunction()} />,
286
+ node
287
+ );
288
+ expect(console.warn.mock.calls.length).toBe(0);
289
+
290
+ React.renderComponent(
291
+ <input type="checkbox" checked="false" readOnly={true} />,
292
+ node
293
+ );
294
+ expect(console.warn.mock.calls.length).toBe(0);
295
+
296
+ React.renderComponent(<input type="checkbox" checked="false" />, node);
297
+ expect(console.warn.mock.calls.length).toBe(1);
298
+
299
+ React.renderComponent(
300
+ <input type="checkbox" checked="false" readOnly={false} />,
301
+ node
302
+ );
303
+ expect(console.warn.mock.calls.length).toBe(2);
304
+ } finally {
305
+ console.warn = oldWarn;
306
+ }
307
+ });
308
+
309
+ it('should throw if both checked and checkedLink are provided', function() {
310
+ // Silences console.error messages
311
+ // ReactErrorUtils.guard is applied to all methods of a React component
312
+ // and calls console.error in __DEV__ (true for test environment)
313
+ spyOn(console, 'error');
314
+
315
+ var node = document.createElement('div');
316
+ var link = new ReactLink(true, mocks.getMockFunction());
317
+ var instance = <input type="checkbox" checkedLink={link} />;
318
+
319
+ expect(React.renderComponent.bind(React, instance, node)).not.toThrow();
320
+
321
+ instance = <input type="checkbox" checkedLink={link} checked="false" />;
322
+ expect(React.renderComponent.bind(React, instance, node)).toThrow();
323
+
324
+ instance = <input type="checkbox" checkedLink={link} onChange={emptyFunction} />;
325
+ expect(React.renderComponent.bind(React, instance, node)).toThrow();
326
+
327
+ });
328
});