The library for web and native user interfaces.
Implement `ReactDOMTextarea`
This changes `ReactDOMTextarea` to accept `defaultValue` and `value`. It will warn people about using children (but allow it and treat it as `defaultValue`, which is the current behavior).
CommitSyncScript committed
Jun 28, 2013 at 16:33 UTC
55176116a225c7664d082580faa6dde12a3bb00e
5 files changed
+224
-162
src/core/ReactDOM.js
+2
-4
@@ -80,8 +80,7 @@ var ReactDOM = objMapKeyVal({
80
embed: true,
81
fieldset: false,
82
footer: false,
83
- // Danger: this gets monkeypatched! See ReactDOMForm for more info.
84
- form: false,
83
+ form: false, // NOTE: Injected, see `ReactDOMForm`.
84
h1: false,
85
h2: false,
86
h3: false,
@@ -116,8 +115,7 @@ var ReactDOM = objMapKeyVal({
115
table: false,
116
tbody: false,
117
td: false,
119
- // Danger: this gets monkeypatched! See ReactDOMTextarea for more info.
120
- textarea: false,
118
+ textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
119
tfoot: false,
120
th: false,
121
thead: false,
src/core/ReactDOMTextarea.js
deleted
-92
@@ -1,92 +0,0 @@
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 ReactDOMTextarea
17
- */
18
-
19
-"use strict";
20
-
21
-var ReactCompositeComponent = require('ReactCompositeComponent');
22
-var ReactDOM = require('ReactDOM');
23
-
24
-var invariant = require('invariant');
25
-
26
-// Store a reference to the <textarea> `ReactNativeComponent`.
27
-var textarea = ReactDOM.textarea;
28
-
29
-// For quickly matching children type, to test if can be treated as content.
30
-var CONTENT_TYPES = {'string': true, 'number': true};
31
-
32
-var getTextContent = function(props) {
33
- if (!props) {
34
- return '';
35
- }
36
-
37
- invariant(
38
- props.dangerouslySetInnerHTML == null,
39
- '`dangerouslySetInnerHTML` does not make sense on textarea.'
40
- );
41
-
42
- var content;
43
- if (Array.isArray(props.children)) {
44
- invariant(
45
- props.children.length <= 1,
46
- 'textarea can have at most one child'
47
- );
48
- content = props.children[0];
49
- } else if (props.children != null) {
50
- content = props.children;
51
- } else {
52
- content = props.content;
53
- }
54
-
55
- invariant(
56
- content == null || CONTENT_TYPES[typeof content],
57
- 'textarea must contain a single string or number, not an array or ' +
58
- 'object.'
59
- );
60
- return content != null ? '' + content : '';
61
-};
62
-
63
-/**
64
- * Since setting .textContent on a dirty <textarea> doesn't update its value,
65
- * we intercept prop changes here to make sure that value is updated.
66
- */
67
-var ReactDOMTextarea = ReactCompositeComponent.createClass({
68
- getInitialState: function() {
69
- // We keep the original value of content or children here so that
70
- // ReactNativeComponent doesn't update textContent (unnecessary since we
71
- // update value).
72
- return {
73
- initialContent: getTextContent(this.props)
74
- };
75
- },
76
-
77
- render: function() {
78
- return this.transferPropsTo(textarea({
79
- content: this.state.initialContent
80
- }));
81
- },
82
-
83
- componentDidUpdate: function(prevProps, prevState, rootNode) {
84
- var oldContent = getTextContent(prevProps);
85
- var newContent = getTextContent(this.props);
86
- if (oldContent !== newContent && rootNode.value !== newContent) {
87
- rootNode.value = newContent;
88
- }
89
- }
90
-});
91
-
92
-module.exports = ReactDOMTextarea;
src/core/__tests__/ReactDOMTextarea-test.js
deleted
-66
@@ -1,66 +0,0 @@
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
- * @jsx React.DOM
17
- * @emails react-core
18
- */
19
-
20
-"use strict";
21
-
22
-describe('ReactDOMTextarea', function() {
23
- var React;
24
- var ReactTestUtils;
25
-
26
- beforeEach(function() {
27
- React = require('React');
28
- ReactTestUtils = require('ReactTestUtils');
29
- });
30
-
31
- it("should update value", function() {
32
- var stub = ReactTestUtils.renderIntoDocument(<textarea>giraffe</textarea>);
33
- var node = stub.getDOMNode();
34
-
35
- expect(node.value).toEqual('giraffe');
36
-
37
- stub.replaceProps({ children: 'gorilla' });
38
- expect(node.value).toEqual('gorilla');
39
-
40
- stub.replaceProps({ children: 17 });
41
- expect(node.value).toEqual('17');
42
-
43
- stub.replaceProps({ children: [42] });
44
- expect(node.value).toEqual('42');
45
-
46
- stub.replaceProps({ children: null });
47
- expect(node.value).toEqual('');
48
-
49
- stub.replaceProps({ content: 'eggplant' });
50
- expect(node.value).toEqual('eggplant');
51
- });
52
-
53
- it("should throw with multiple or invalid children", function() {
54
- expect(function() {
55
- ReactTestUtils.renderIntoDocument(
56
- <textarea>{'hello'}{'there'}</textarea>
57
- );
58
- }).toThrow();
59
-
60
- expect(function() {
61
- ReactTestUtils.renderIntoDocument(
62
- <textarea><strong /></textarea>
63
- );
64
- }).toThrow();
65
- });
66
-});
src/dom/components/ReactDOMTextarea.js
new
+129
@@ -0,0 +1,129 @@
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 ReactDOMTextarea
17
+ */
18
+
19
+"use strict";
20
+
21
+var DOMPropertyOperations = require('DOMPropertyOperations');
22
+var ReactCompositeComponent = require('ReactCompositeComponent');
23
+var ReactDOM = require('ReactDOM');
24
+
25
+var invariant = require('invariant');
26
+var merge = require('merge');
27
+
28
+// Store a reference to the <textarea> `ReactNativeComponent`.
29
+var textarea = ReactDOM.textarea;
30
+
31
+// For quickly matching children type, to test if can be treated as content.
32
+var CONTENT_TYPES = {'string': true, 'number': true};
33
+
34
+/**
35
+ * Implements a <textarea> native component that allows setting `value`, and
36
+ * `defaultValue`. This differs from the traditional DOM API because value is
37
+ * usually set as PCDATA children.
38
+ *
39
+ * If `value` is not supplied (or null/undefined), user actions that affect the
40
+ * value will trigger updates to the element.
41
+ *
42
+ * If `value` is supplied (and not null/undefined), the rendered element will
43
+ * not trigger updates to the element. Instead, the `value` prop must change in
44
+ * order for the rendered element to be updated.
45
+ *
46
+ * The rendered element will be initialized with an empty value, the prop
47
+ * `defaultValue` if specified, or the children content (deprecated).
48
+ */
49
+var ReactDOMTextarea = ReactCompositeComponent.createClass({
50
+
51
+ getInitialState: function() {
52
+ var defaultValue = this.props.defaultValue;
53
+ // TODO (yungsters): Remove support for children content in <textarea>.
54
+ var children = this.props.children;
55
+ if (children != null) {
56
+ global.console && console.warn && console.warn(
57
+ 'Use the `defaultValue` or `value` props instead of setting children ' +
58
+ 'on <textarea>.'
59
+ );
60
+ invariant(
61
+ defaultValue == null,
62
+ 'If you supply `defaultValue` on a <textarea>, do not pass children.'
63
+ );
64
+ if (Array.isArray(children)) {
65
+ invariant(
66
+ children.length <= 1,
67
+ '<textarea> can only have at most one child.'
68
+ );
69
+ children = children[0];
70
+ }
71
+ invariant(
72
+ CONTENT_TYPES[typeof children],
73
+ 'If you specify children to <textarea>, it must be a single string ' +
74
+ 'or number., not an array or object.'
75
+ );
76
+ defaultValue = '' + children;
77
+ }
78
+ defaultValue = defaultValue || '';
79
+ return {
80
+ // We save the initial value so that `ReactNativeComponent` doesn't update
81
+ // `textContent` (unnecessary since we update value).
82
+ initialValue: this.props.value != null ? this.props.value : defaultValue,
83
+ value: defaultValue
84
+ };
85
+ },
86
+
87
+ getValue: function() {
88
+ return this.props.value != null ? this.props.value : this.state.value;
89
+ },
90
+
91
+ render: function() {
92
+ // Clone `this.props` so we don't mutate the input.
93
+ var props = merge(this.props);
94
+
95
+ invariant(
96
+ props.dangerouslySetInnerHTML == null,
97
+ '`dangerouslySetInnerHTML` does not make sense on <textarea>.'
98
+ );
99
+
100
+ props.value = this.getValue();
101
+ props.onChange = this.handleChange;
102
+
103
+ // Always set children to the same thing. In IE9, the selection range will
104
+ // get reset if `textContent` is mutated.
105
+ return textarea(props, this.state.initialValue);
106
+ },
107
+
108
+ componentDidUpdate: function(prevProps, prevState, rootNode) {
109
+ if (this.props.value != null) {
110
+ DOMPropertyOperations.setValueForProperty(
111
+ rootNode,
112
+ 'value',
113
+ this.props.value || ''
114
+ );
115
+ }
116
+ },
117
+
118
+ handleChange: ReactCompositeComponent.autoBind(function(event) {
119
+ var returnValue;
120
+ if (this.props.onChange) {
121
+ returnValue = this.props.onChange(event);
122
+ }
123
+ this.setState({value: event.target.value});
124
+ return returnValue;
125
+ })
126
+
127
+});
128
+
129
+module.exports = ReactDOMTextarea;
src/dom/components/__tests__/ReactDOMTextarea-test.js
new
+93
@@ -0,0 +1,93 @@
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
+ * @jsx React.DOM
17
+ * @emails react-core
18
+ */
19
+
20
+"use strict";
21
+
22
+/*jshint evil:true */
23
+
24
+describe('ReactDOMTextarea', function() {
25
+ var React;
26
+ var ReactTestUtils;
27
+
28
+ var renderTextarea;
29
+
30
+ beforeEach(function() {
31
+ React = require('React');
32
+ ReactTestUtils = require('ReactTestUtils');
33
+
34
+ renderTextarea = function(component) {
35
+ var stub = ReactTestUtils.renderIntoDocument(component);
36
+ var node = stub.getDOMNode();
37
+ // Polyfilling the browser's quirky behavior.
38
+ node.value = node.innerHTML;
39
+ return node;
40
+ };
41
+ });
42
+
43
+ it('should allow setting `defaultValue`', function() {
44
+ var stub = <textarea defaultValue="giraffe" />;
45
+ var node = renderTextarea(stub);
46
+
47
+ expect(node.value).toBe('giraffe');
48
+
49
+ // Changing `defaultValue` should do nothing.
50
+ stub.replaceProps({defaultValue: 'gorilla'});
51
+ expect(node.value).toEqual('giraffe');
52
+ });
53
+
54
+ it('should allow setting `value`', function() {
55
+ var stub = <textarea value="giraffe" />;
56
+ var node = renderTextarea(stub);
57
+
58
+ expect(node.value).toBe('giraffe');
59
+
60
+ stub.replaceProps({value: 'gorilla'});
61
+ expect(node.value).toEqual('gorilla');
62
+ });
63
+
64
+ it('should treat children like `defaultValue`', function() {
65
+ var stub = <textarea>giraffe</textarea>;
66
+ var node = renderTextarea(stub);
67
+
68
+ expect(node.value).toBe('giraffe');
69
+
70
+ // Changing children should do nothing, it functions like `defaultValue`.
71
+ stub.replaceProps({children: 'gorilla'});
72
+ expect(node.value).toEqual('giraffe');
73
+ });
74
+
75
+ it('should allow numbers as children', function() {
76
+ var node = renderTextarea(<textarea>{17}</textarea>);
77
+ expect(node.value).toBe('17');
78
+ });
79
+
80
+ it("should throw with multiple or invalid children", function() {
81
+ expect(function() {
82
+ ReactTestUtils.renderIntoDocument(
83
+ <textarea>{'hello'}{'there'}</textarea>
84
+ );
85
+ }).toThrow();
86
+
87
+ expect(function() {
88
+ ReactTestUtils.renderIntoDocument(
89
+ <textarea><strong /></textarea>
90
+ );
91
+ }).toThrow();
92
+ });
93
+});