The library for web and native user interfaces.
Controlled <select> and <option> Components
This implements a `<select>` component that supports `value` and `defaultValue`. It also changes `<option>` to warn when the `selected` prop is supplied.
Tim Yung committed
Jul 16, 2013 at 11:33 UTC
ed54fff2046fe6093078f625280725fcb19b9b33
3 files changed
+210
src/core/ReactDefaultInjection.js
+4
@@ -21,6 +21,8 @@
21
var ReactDOM = require('ReactDOM');
22
var ReactDOMForm = require('ReactDOMForm');
23
var ReactDOMInput = require('ReactDOMInput');
24
+var ReactDOMOption = require('ReactDOMOption');
25
+var ReactDOMSelect = require('ReactDOMSelect');
26
var ReactDOMTextarea = require('ReactDOMTextarea');
27
28
var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
@@ -53,6 +55,8 @@ function inject() {
55
ReactDOM.injection.injectComponentClasses({
56
form: ReactDOMForm,
57
input: ReactDOMInput,
58
+ option: ReactDOMOption,
59
+ select: ReactDOMSelect,
60
textarea: ReactDOMTextarea
61
});
62
src/dom/components/ReactDOMOption.js
new
+50
@@ -0,0 +1,50 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactDOMOption
17
+ */
18
+
19
+"use strict";
20
+
21
+var ReactCompositeComponent = require('ReactCompositeComponent');
22
+var ReactDOM = require('ReactDOM');
23
+
24
+// Store a reference to the <option> `ReactNativeComponent`.
25
+var option = ReactDOM.option;
26
+
27
+/**
28
+ * Implements an <option> native component that warns when `selected` is set.
29
+ */
30
+var ReactDOMOption = ReactCompositeComponent.createClass({
31
+
32
+ componentWillMount: function() {
33
+ // TODO (yungsters): Remove support for `selected` in <option>.
34
+ if (this.props.selected != null) {
35
+ if (__DEV__) {
36
+ console.warn(
37
+ 'Use the `defaultValue` or `value` props on <select> instead of ' +
38
+ 'setting `selected` on <option>.'
39
+ );
40
+ }
41
+ }
42
+ },
43
+
44
+ render: function() {
45
+ return option(this.props, this.props.children);
46
+ }
47
+
48
+});
49
+
50
+module.exports = ReactDOMOption;
src/dom/components/ReactDOMSelect.js
new
+156
@@ -0,0 +1,156 @@
1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactDOMSelect
17
+ */
18
+
19
+"use strict";
20
+
21
+var ReactCompositeComponent = require('ReactCompositeComponent');
22
+var ReactDOM = require('ReactDOM');
23
+
24
+var invariant = require('invariant');
25
+var merge = require('merge');
26
+
27
+// Store a reference to the <select> `ReactNativeComponent`.
28
+var select = ReactDOM.select;
29
+
30
+/**
31
+ * Validation function for `value` and `defaultValue`.
32
+ * @private
33
+ */
34
+function selectValueType(props, propName, componentName) {
35
+ if (props[propName] == null) {
36
+ return;
37
+ }
38
+ if (props.multiple) {
39
+ invariant(
40
+ Array.isArray(props[propName]),
41
+ 'The `%s` prop supplied to <select> must be an array if `multiple` is ' +
42
+ 'true.'
43
+ );
44
+ } else {
45
+ invariant(
46
+ !Array.isArray(props[propName]),
47
+ 'The `%s` prop supplied to <select> must be a scalar value if ' +
48
+ '`multiple` is false.'
49
+ );
50
+ }
51
+}
52
+
53
+/**
54
+ * If `value` is supplied, updates <option> elements on mount and update.
55
+ * @private
56
+ */
57
+function updateOptions() {
58
+ /*jshint validthis:true */
59
+ if (this.props.value == null) {
60
+ return;
61
+ }
62
+ var options = this.getDOMNode().options;
63
+ var selectedValue = '' + this.props.value;
64
+
65
+ for (var i = 0, l = options.length; i < l; i++) {
66
+ var selected = this.props.multiple ?
67
+ selectedValue.indexOf(options[i].value) >= 0 :
68
+ selected = options[i].value === selectedValue;
69
+
70
+ if (selected !== options[i].selected) {
71
+ options[i].selected = selected;
72
+ }
73
+ }
74
+}
75
+
76
+/**
77
+ * Implements a <select> native component that allows optionally setting the
78
+ * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
79
+ * string. If `multiple` is true, the prop must be an array of strings.
80
+ *
81
+ * If `value` is not supplied (or null/undefined), user actions that change the
82
+ * selected option will trigger updates to the rendered options.
83
+ *
84
+ * If it is supplied (and not null/undefined), the rendered options will not
85
+ * update in response to user actions. Instead, the `value` prop must change in
86
+ * order for the rendered options to update.
87
+ *
88
+ * If `defaultValue` is provided, any options with the supplied values will be
89
+ * selected.
90
+ */
91
+var ReactDOMSelect = ReactCompositeComponent.createClass({
92
+
93
+ propTypes: {
94
+ defaultValue: selectValueType,
95
+ value: selectValueType
96
+ },
97
+
98
+ getInitialState: function() {
99
+ return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
100
+ },
101
+
102
+ componentWillReceiveProps: function(nextProps) {
103
+ if (!this.props.multiple && nextProps.multiple) {
104
+ this.setState({value: [this.state.value]});
105
+ } else if (this.props.multiple && !nextProps.multiple) {
106
+ this.setState({value: this.state.value[0]});
107
+ }
108
+ },
109
+
110
+ shouldComponentUpdate: function() {
111
+ // Defer any updates to this component during the `onChange` handler.
112
+ return !this._isChanging;
113
+ },
114
+
115
+ render: function() {
116
+ // Clone `this.props` so we don't mutate the input.
117
+ var props = merge(this.props);
118
+
119
+ props.onChange = this.handleChange;
120
+ props.value = null;
121
+
122
+ return select(props, this.props.children);
123
+ },
124
+
125
+ componentDidMount: updateOptions,
126
+
127
+ componentDidUpdate: updateOptions,
128
+
129
+ handleChange: function(event) {
130
+ var returnValue;
131
+ if (this.props.onChange) {
132
+ this._isChanging = true;
133
+ returnValue = this.props.onChange(event);
134
+ this._isChanging = false;
135
+ }
136
+
137
+ var selectedValue;
138
+ if (this.props.multiple) {
139
+ selectedValue = [];
140
+ var options = event.target.options;
141
+ for (var i = 0, l = options.length; i < l; i++) {
142
+ if (options[i].selected) {
143
+ selectedValue.push(options[i].value);
144
+ }
145
+ }
146
+ } else {
147
+ selectedValue = event.target.value;
148
+ }
149
+
150
+ this.setState({value: selectedValue});
151
+ return returnValue;
152
+ }
153
+
154
+});
155
+
156
+module.exports = ReactDOMSelect;