@samitouri / QOS-React-2 / commits / 52a229f168

Make DOM wrapper component using lower-level primitives

Introducing: a really lame version of composite components, right inside of ReactDOMComponent! Now ReactDOMInput isn't an actual component. This brings us closer to exposing DOM nodes as refs.

Ben Alpert committed May 28, 2015 at 16:06 UTC 52a229f168ea10c342d0e69e1ddc63d425579656
10 files changed +270 -198
src/renderers/dom/client/wrappers/AutoFocusUtils.js renamed
+13 -3
@@ -6,16 +6,18 @@
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 - * @providesModule AutoFocusMixin
9 + * @providesModule AutoFocusUtils
10 * @typechecks static-only
11 */
12
13 'use strict';
14
15 +var ReactMount = require('ReactMount');
16 +
17 var findDOMNode = require('findDOMNode');
18 var focusNode = require('focusNode');
19
18 -var AutoFocusMixin = {
20 +var Mixin = {
21 componentDidMount: function() {
22 if (this.props.autoFocus) {
23 focusNode(findDOMNode(this));
@@ -23,4 +25,12 @@ var AutoFocusMixin = {
25 }
26 };
27
26 -module.exports = AutoFocusMixin;
28 +var AutoFocusUtils = {
29 + Mixin: Mixin,
30 +
31 + focusDOMComponent: function() {
32 + focusNode(ReactMount.getNode(this._rootNodeID));
33 + }
34 +};
35 +
36 +module.exports = AutoFocusUtils;
src/renderers/dom/client/wrappers/LinkedValueUtils.js
+61 -50
@@ -15,6 +15,7 @@
15 var ReactPropTypes = require('ReactPropTypes');
16
17 var invariant = require('invariant');
18 +var warning = require('warning');
19
20 var hasReadOnlyValue = {
21 'button': true,
@@ -52,20 +53,48 @@ function _assertCheckedLink(inputProps) {
53 );
54 }
55
55 -/**
56 - * @param {SyntheticEvent} e change event to handle
57 - */
58 -function _handleLinkedValueChange(e) {
59 - /*jshint validthis:true */
60 - this.props.valueLink.requestChange(e.target.value);
61 -}
56 +var propTypes = {
57 + value: function(props, propName, componentName) {
58 + if (!props[propName] ||
59 + hasReadOnlyValue[props.type] ||
60 + props.onChange ||
61 + props.readOnly ||
62 + props.disabled) {
63 + return null;
64 + }
65 + return new Error(
66 + 'You provided a `value` prop to a form field without an ' +
67 + '`onChange` handler. This will render a read-only field. If ' +
68 + 'the field should be mutable use `defaultValue`. Otherwise, ' +
69 + 'set either `onChange` or `readOnly`.'
70 + );
71 + },
72 + checked: function(props, propName, componentName) {
73 + if (!props[propName] ||
74 + props.onChange ||
75 + props.readOnly ||
76 + props.disabled) {
77 + return null;
78 + }
79 + return new Error(
80 + 'You provided a `checked` prop to a form field without an ' +
81 + '`onChange` handler. This will render a read-only field. If ' +
82 + 'the field should be mutable use `defaultChecked`. Otherwise, ' +
83 + 'set either `onChange` or `readOnly`.'
84 + );
85 + },
86 + onChange: ReactPropTypes.func
87 +};
88
63 -/**
64 - * @param {SyntheticEvent} e change event to handle
65 - */
66 -function _handleLinkedCheckChange(e) {
67 - /*jshint validthis:true */
68 - this.props.checkedLink.requestChange(e.target.checked);
89 +var loggedTypeFailures = {};
90 +function getDeclarationErrorAddendum(owner) {
91 + if (owner) {
92 + var name = owner.getName();
93 + if (name) {
94 + return ' Check the render method of `' + name + '`.';
95 + }
96 + }
97 + return '';
98 }
99
100 /**
@@ -73,38 +102,19 @@ function _handleLinkedCheckChange(e) {
102 * this outside of the ReactDOM controlled form components.
103 */
104 var LinkedValueUtils = {
76 - Mixin: {
77 - propTypes: {
78 - value: function(props, propName, componentName) {
79 - if (!props[propName] ||
80 - hasReadOnlyValue[props.type] ||
81 - props.onChange ||
82 - props.readOnly ||
83 - props.disabled) {
84 - return null;
85 - }
86 - return new Error(
87 - 'You provided a `value` prop to a form field without an ' +
88 - '`onChange` handler. This will render a read-only field. If ' +
89 - 'the field should be mutable use `defaultValue`. Otherwise, ' +
90 - 'set either `onChange` or `readOnly`.'
91 - );
92 - },
93 - checked: function(props, propName, componentName) {
94 - if (!props[propName] ||
95 - props.onChange ||
96 - props.readOnly ||
97 - props.disabled) {
98 - return null;
99 - }
100 - return new Error(
101 - 'You provided a `checked` prop to a form field without an ' +
102 - '`onChange` handler. This will render a read-only field. If ' +
103 - 'the field should be mutable use `defaultChecked`. Otherwise, ' +
104 - 'set either `onChange` or `readOnly`.'
105 - );
106 - },
107 - onChange: ReactPropTypes.func
105 + checkPropTypes: function (tagName, props, owner) {
106 + for (var propName in propTypes) {
107 + if (propTypes.hasOwnProperty(propName)) {
108 + var error = propTypes[propName](props, propName, tagName, location);
109 + }
110 + if (error instanceof Error && !(error.message in loggedTypeFailures)) {
111 + // Only monitor this failure once because there tends to be a lot of the
112 + // same error.
113 + loggedTypeFailures[error.message] = true;
114 +
115 + var addendum = getDeclarationErrorAddendum(owner);
116 + warning(false, 'Failed form propType: %s%s', error.message, addendum);
117 + }
118 }
119 },
120
@@ -135,17 +145,18 @@ var LinkedValueUtils = {
145
146 /**
147 * @param {object} inputProps Props for form component
138 - * @return {function} change callback either from onChange prop or link.
148 + * @param {SyntheticEvent} event change event to handle
149 */
140 - getOnChange: function(inputProps) {
150 + executeOnChange: function(inputProps, event) {
151 if (inputProps.valueLink) {
152 _assertValueLink(inputProps);
143 - return _handleLinkedValueChange;
153 + return inputProps.valueLink.requestChange(event.target.value);
154 } else if (inputProps.checkedLink) {
155 _assertCheckedLink(inputProps);
146 - return _handleLinkedCheckChange;
156 + return inputProps.checkedLink.requestChange(event.target.checked);
157 + } else if (inputProps.onChange) {
158 + return inputProps.onChange.call(undefined, event);
159 }
148 - return inputProps.onChange;
160 }
161 };
162
src/renderers/dom/client/wrappers/ReactDOMButton.js
+2 -2
@@ -11,7 +11,7 @@
11
12 'use strict';
13
14 -var AutoFocusMixin = require('AutoFocusMixin');
14 +var AutoFocusUtils = require('AutoFocusUtils');
15 var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
16 var ReactClass = require('ReactClass');
17 var ReactElement = require('ReactElement');
@@ -41,7 +41,7 @@ var ReactDOMButton = ReactClass.createClass({
41 displayName: 'ReactDOMButton',
42 tagName: 'BUTTON',
43
44 - mixins: [AutoFocusMixin, ReactBrowserComponentMixin],
44 + mixins: [AutoFocusUtils.Mixin, ReactBrowserComponentMixin],
45
46 render: function() {
47 var props = {};
src/renderers/dom/client/wrappers/ReactDOMInput.js
+108 -105
@@ -11,27 +11,22 @@
11
12 'use strict';
13
14 -var AutoFocusMixin = require('AutoFocusMixin');
15 -var DOMPropertyOperations = require('DOMPropertyOperations');
14 +var AutoFocusUtils = require('AutoFocusUtils');
15 +var ReactDOMIDOperations = require('ReactDOMIDOperations');
16 var LinkedValueUtils = require('LinkedValueUtils');
17 -var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
18 -var ReactClass = require('ReactClass');
19 -var ReactElement = require('ReactElement');
17 var ReactMount = require('ReactMount');
18 var ReactUpdates = require('ReactUpdates');
19
20 var assign = require('Object.assign');
24 -var findDOMNode = require('findDOMNode');
21 var invariant = require('invariant');
22
27 -var input = ReactElement.createFactory('input');
28 -
23 var instancesByReactID = {};
24
25 function forceUpdateIfMounted() {
26 /*jshint validthis:true */
33 - if (this.isMounted()) {
34 - this.forceUpdate();
27 + if (this._rootNodeID) {
28 + // DOM component is still mounted; update
29 + ReactDOMInput.updateWrapper(this);
30 }
31 }
32
@@ -51,124 +46,132 @@ function forceUpdateIfMounted() {
46 *
47 * @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
48 */
54 -var ReactDOMInput = ReactClass.createClass({
55 - displayName: 'ReactDOMInput',
56 - tagName: 'INPUT',
57 -
58 - mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
59 -
60 - getInitialState: function() {
61 - var defaultValue = this.props.defaultValue;
62 - return {
63 - initialChecked: this.props.defaultChecked || false,
64 - initialValue: defaultValue != null ? defaultValue : null
65 - };
49 +var ReactDOMInput = {
50 + getNativeProps: function(inst, props, context) {
51 + var value = LinkedValueUtils.getValue(props);
52 + var checked = LinkedValueUtils.getChecked(props);
53 +
54 + var nativeProps = assign({}, props, {
55 + defaultChecked: undefined,
56 + defaultValue: undefined,
57 + value: value != null ? value : inst._wrapperState.initialValue,
58 + checked: checked != null ? checked : inst._wrapperState.initialChecked,
59 + onChange: inst._wrapperState.onChange
60 + });
61 +
62 + return nativeProps;
63 },
64
68 - render: function() {
69 - // Clone `this.props` so we don't mutate the input.
70 - var props = assign({}, this.props);
71 -
72 - props.defaultChecked = null;
73 - props.defaultValue = null;
74 -
75 - var value = LinkedValueUtils.getValue(this.props);
76 - props.value = value != null ? value : this.state.initialValue;
77 -
78 - var checked = LinkedValueUtils.getChecked(this.props);
79 - props.checked = checked != null ? checked : this.state.initialChecked;
80 -
81 - props.onChange = this._handleChange;
65 + mountWrapper: function(inst, props) {
66 + LinkedValueUtils.checkPropTypes(
67 + 'input',
68 + props,
69 + inst._currentElement._owner
70 + );
71 +
72 + var defaultValue = props.defaultValue;
73 + inst._wrapperState = {
74 + initialChecked: props.defaultChecked || false,
75 + initialValue: defaultValue != null ? defaultValue : null,
76 + onChange: _handleChange.bind(inst)
77 + };
78
83 - return input(props, this.props.children);
79 + instancesByReactID[inst._rootNodeID] = inst;
80 },
81
86 - componentDidMount: function() {
87 - var id = ReactMount.getID(findDOMNode(this));
88 - instancesByReactID[id] = this;
82 + postMountWrapper: function(inst, transaction, props) {
83 + if (props.autoFocus) {
84 + transaction.getReactMountReady().enqueue(
85 + AutoFocusUtils.focusDOMComponent,
86 + inst
87 + );
88 + }
89 },
90
91 - componentWillUnmount: function() {
92 - var rootNode = findDOMNode(this);
93 - var id = ReactMount.getID(rootNode);
94 - delete instancesByReactID[id];
91 + unmountWrapper: function(inst) {
92 + delete instancesByReactID[inst._rootNodeID];
93 },
94
97 - componentDidUpdate: function(prevProps, prevState, prevContext) {
98 - var rootNode = findDOMNode(this);
99 - if (this.props.checked != null) {
100 - DOMPropertyOperations.setValueForProperty(
101 - rootNode,
95 + updateWrapper: function(inst) {
96 + var props = inst._currentElement.props;
97 +
98 + // TODO: Shouldn't this be getChecked(props)?
99 + var checked = props.checked;
100 + if (checked != null) {
101 + ReactDOMIDOperations.updatePropertyByID(
102 + inst._rootNodeID,
103 'checked',
103 - this.props.checked || false
104 + checked || false
105 );
106 }
107
107 - var value = LinkedValueUtils.getValue(this.props);
108 + var value = LinkedValueUtils.getValue(props);
109 if (value != null) {
110 // Cast `value` to a string to ensure the value is set correctly. While
111 // browsers typically do this as necessary, jsdom doesn't.
111 - DOMPropertyOperations.setValueForProperty(rootNode, 'value', '' + value);
112 + ReactDOMIDOperations.updatePropertyByID(
113 + inst._rootNodeID,
114 + 'value',
115 + '' + value
116 + );
117 }
113 - },
118 + }
119 +};
120 +
121 +function _handleChange(event) {
122 + var props = this._currentElement.props;
123 +
124 + var returnValue = LinkedValueUtils.executeOnChange(props, event);
125 +
126 + // Here we use asap to wait until all updates have propagated, which
127 + // is important when using controlled components within layers:
128 + // https://github.com/facebook/react/issues/1698
129 + ReactUpdates.asap(forceUpdateIfMounted, this);
130
115 - _handleChange: function(event) {
116 - var returnValue;
117 - var onChange = LinkedValueUtils.getOnChange(this.props);
118 - if (onChange) {
119 - returnValue = onChange.call(this, event);
131 + var name = props.name;
132 + if (props.type === 'radio' && name != null) {
133 + var rootNode = ReactMount.getNode(this._rootNodeID);
134 + var queryRoot = rootNode;
135 +
136 + while (queryRoot.parentNode) {
137 + queryRoot = queryRoot.parentNode;
138 }
121 - // Here we use asap to wait until all updates have propagated, which
122 - // is important when using controlled components within layers:
123 - // https://github.com/facebook/react/issues/1698
124 - ReactUpdates.asap(forceUpdateIfMounted, this);
125 -
126 - var name = this.props.name;
127 - if (this.props.type === 'radio' && name != null) {
128 - var rootNode = findDOMNode(this);
129 - var queryRoot = rootNode;
130 -
131 - while (queryRoot.parentNode) {
132 - queryRoot = queryRoot.parentNode;
133 - }
139
135 - // If `rootNode.form` was non-null, then we could try `form.elements`,
136 - // but that sometimes behaves strangely in IE8. We could also try using
137 - // `form.getElementsByName`, but that will only return direct children
138 - // and won't include inputs that use the HTML5 `form=` attribute. Since
139 - // the input might not even be in a form, let's just use the global
140 - // `querySelectorAll` to ensure we don't miss anything.
141 - var group = queryRoot.querySelectorAll(
142 - 'input[name=' + JSON.stringify('' + name) + '][type="radio"]');
143 -
144 - for (var i = 0; i < group.length; i++) {
145 - var otherNode = group[i];
146 - if (otherNode === rootNode ||
147 - otherNode.form !== rootNode.form) {
148 - continue;
149 - }
150 - var otherID = ReactMount.getID(otherNode);
151 - invariant(
152 - otherID,
153 - 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
154 - 'same `name` is not supported.'
155 - );
156 - var otherInstance = instancesByReactID[otherID];
157 - invariant(
158 - otherInstance,
159 - 'ReactDOMInput: Unknown radio button ID %s.',
160 - otherID
161 - );
162 - // If this is a controlled radio button group, forcing the input that
163 - // was previously checked to update will cause it to be come re-checked
164 - // as appropriate.
165 - ReactUpdates.asap(forceUpdateIfMounted, otherInstance);
140 + // If `rootNode.form` was non-null, then we could try `form.elements`,
141 + // but that sometimes behaves strangely in IE8. We could also try using
142 + // `form.getElementsByName`, but that will only return direct children
143 + // and won't include inputs that use the HTML5 `form=` attribute. Since
144 + // the input might not even be in a form, let's just use the global
145 + // `querySelectorAll` to ensure we don't miss anything.
146 + var group = queryRoot.querySelectorAll(
147 + 'input[name=' + JSON.stringify('' + name) + '][type="radio"]');
148 +
149 + for (var i = 0; i < group.length; i++) {
150 + var otherNode = group[i];
151 + if (otherNode === rootNode ||
152 + otherNode.form !== rootNode.form) {
153 + continue;
154 }
155 + var otherID = ReactMount.getID(otherNode);
156 + invariant(
157 + otherID,
158 + 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
159 + 'same `name` is not supported.'
160 + );
161 + var otherInstance = instancesByReactID[otherID];
162 + invariant(
163 + otherInstance,
164 + 'ReactDOMInput: Unknown radio button ID %s.',
165 + otherID
166 + );
167 + // If this is a controlled radio button group, forcing the input that
168 + // was previously checked to update will cause it to be come re-checked
169 + // as appropriate.
170 + ReactUpdates.asap(forceUpdateIfMounted, otherInstance);
171 }
168 -
169 - return returnValue;
172 }
173
172 -});
174 + return returnValue;
175 +}
176
177 module.exports = ReactDOMInput;
src/renderers/dom/client/wrappers/ReactDOMSelect.js
+14 -11
@@ -11,11 +11,12 @@
11
12 'use strict';
13
14 -var AutoFocusMixin = require('AutoFocusMixin');
14 +var AutoFocusUtils = require('AutoFocusUtils');
15 var LinkedValueUtils = require('LinkedValueUtils');
16 var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
17 var ReactClass = require('ReactClass');
18 var ReactElement = require('ReactElement');
19 +var ReactInstanceMap = require('ReactInstanceMap');
20 var ReactUpdates = require('ReactUpdates');
21 var ReactPropTypes = require('ReactPropTypes');
22
@@ -118,7 +119,7 @@ var ReactDOMSelect = ReactClass.createClass({
119 displayName: 'ReactDOMSelect',
120 tagName: 'SELECT',
121
121 - mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
122 + mixins: [AutoFocusUtils.Mixin, ReactBrowserComponentMixin],
123
124 statics: {
125 valueContextKey: valueContextKey
@@ -129,6 +130,16 @@ var ReactDOMSelect = ReactClass.createClass({
130 value: selectValueType
131 },
132
133 + componentWillMount: function() {
134 + LinkedValueUtils.checkPropTypes(
135 + 'select',
136 + this.props,
137 + ReactInstanceMap.get(this)._currentElement._owner
138 + );
139 +
140 + this._pendingUpdate = false;
141 + },
142 +
143 getInitialState: function() {
144 // Pass down initial value so initial generated markup has correct
145 // `selected` attributes
@@ -162,10 +173,6 @@ var ReactDOMSelect = ReactClass.createClass({
173 return select(props, this.props.children);
174 },
175
165 - componentWillMount: function() {
166 - this._pendingUpdate = false;
167 - },
168 -
176 componentWillReceiveProps: function(nextProps) {
177 // After the initial mount, we control selected-ness manually so don't pass
178 // the context value down
@@ -189,11 +196,7 @@ var ReactDOMSelect = ReactClass.createClass({
196 },
197
198 _handleChange: function(event) {
192 - var returnValue;
193 - var onChange = LinkedValueUtils.getOnChange(this.props);
194 - if (onChange) {
195 - returnValue = onChange.call(this, event);
196 - }
199 + var returnValue = LinkedValueUtils.executeOnChange(this.props, event);
200
201 this._pendingUpdate = true;
202 ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this);
src/renderers/dom/client/wrappers/ReactDOMTextarea.js
+12 -7
@@ -11,12 +11,13 @@
11
12 'use strict';
13
14 -var AutoFocusMixin = require('AutoFocusMixin');
14 +var AutoFocusUtils = require('AutoFocusUtils');
15 var DOMPropertyOperations = require('DOMPropertyOperations');
16 var LinkedValueUtils = require('LinkedValueUtils');
17 var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
18 var ReactClass = require('ReactClass');
19 var ReactElement = require('ReactElement');
20 +var ReactInstanceMap = require('ReactInstanceMap');
21 var ReactUpdates = require('ReactUpdates');
22
23 var assign = require('Object.assign');
@@ -53,7 +54,15 @@ var ReactDOMTextarea = ReactClass.createClass({
54 displayName: 'ReactDOMTextarea',
55 tagName: 'TEXTAREA',
56
56 - mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
57 + mixins: [AutoFocusUtils.Mixin, ReactBrowserComponentMixin],
58 +
59 + componentWillMount: function() {
60 + LinkedValueUtils.checkPropTypes(
61 + 'textarea',
62 + this.props,
63 + ReactInstanceMap.get(this)._currentElement._owner
64 + );
65 + },
66
67 getInitialState: function() {
68 var defaultValue = this.props.defaultValue;
@@ -123,11 +132,7 @@ var ReactDOMTextarea = ReactClass.createClass({
132 },
133
134 _handleChange: function(event) {
126 - var returnValue;
127 - var onChange = LinkedValueUtils.getOnChange(this.props);
128 - if (onChange) {
129 - returnValue = onChange.call(this, event);
130 - }
135 + var returnValue = LinkedValueUtils.executeOnChange(this.props, event);
136 ReactUpdates.asap(forceUpdateIfMounted, this);
137 return returnValue;
138 }
src/renderers/dom/shared/HTMLDOMPropertyConfig.js
+1 -1
@@ -54,7 +54,7 @@ var HTMLDOMPropertyConfig = {
54 alt: null,
55 async: HAS_BOOLEAN_VALUE,
56 autoComplete: null,
57 - // autoFocus is polyfilled/normalized by AutoFocusMixin
57 + // autoFocus is polyfilled/normalized by AutoFocusUtils
58 // autoFocus: HAS_BOOLEAN_VALUE,
59 autoPlay: HAS_BOOLEAN_VALUE,
60 capture: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
src/renderers/dom/shared/ReactDOMComponent.js
+52 -15
@@ -20,6 +20,7 @@ var DOMPropertyOperations = require('DOMPropertyOperations');
20 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
21 var ReactComponentBrowserEnvironment =
22 require('ReactComponentBrowserEnvironment');
23 +var ReactDOMInput = require('ReactDOMInput');
24 var ReactMount = require('ReactMount');
25 var ReactMultiChild = require('ReactMultiChild');
26 var ReactPerf = require('ReactPerf');
@@ -254,6 +255,7 @@ function ReactDOMComponent(tag) {
255 this._previousStyle = null;
256 this._previousStyleCopy = null;
257 this._rootNodeID = null;
258 + this._wrapperState = null;
259 }
260
261 ReactDOMComponent.displayName = 'ReactDOMComponent';
@@ -277,7 +279,16 @@ ReactDOMComponent.Mixin = {
279 mountComponent: function(rootID, transaction, context) {
280 this._rootNodeID = rootID;
281
280 - assertValidProps(this, this._currentElement.props);
282 + var props = this._currentElement.props;
283 +
284 + switch (this._tag) {
285 + case 'input':
286 + ReactDOMInput.mountWrapper(this, props);
287 + props = ReactDOMInput.getNativeProps(this, props, context);
288 + break;
289 + }
290 +
291 + assertValidProps(this, props);
292 if (__DEV__) {
293 if (context[validateDOMNesting.ancestorInfoContextKey]) {
294 validateDOMNesting(
@@ -288,8 +299,15 @@ ReactDOMComponent.Mixin = {
299 }
300 }
301
291 - var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction);
292 - var tagContent = this._createContentMarkup(transaction, context);
302 + var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
303 + var tagContent = this._createContentMarkup(transaction, props, context);
304 +
305 + switch (this._tag) {
306 + case 'input':
307 + ReactDOMInput.postMountWrapper(this, transaction, props);
308 + break;
309 + }
310 +
311 if (!tagContent && omittedCloseTags[this._tag]) {
312 return tagOpen + '/>';
313 }
@@ -306,10 +324,10 @@ ReactDOMComponent.Mixin = {
324 *
325 * @private
326 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
327 + * @param {object} props
328 * @return {string} Markup of opening tag.
329 */
311 - _createOpenTagMarkupAndPutListeners: function(transaction) {
312 - var props = this._currentElement.props;
330 + _createOpenTagMarkupAndPutListeners: function(transaction, props) {
331 var ret = '<' + this._tag;
332
333 for (var propKey in props) {
@@ -356,12 +374,12 @@ ReactDOMComponent.Mixin = {
374 *
375 * @private
376 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
377 + * @param {object} props
378 * @param {object} context
379 * @return {string} Content markup.
380 */
362 - _createContentMarkup: function(transaction, context) {
381 + _createContentMarkup: function(transaction, props, context) {
382 var ret = '';
364 - var props = this._currentElement.props;
383
384 // Intentional use of != to avoid catching zero/false.
385 var innerHTML = props.dangerouslySetInnerHTML;
@@ -427,10 +445,22 @@ ReactDOMComponent.Mixin = {
445 * @overridable
446 */
447 updateComponent: function(transaction, prevElement, nextElement, context) {
430 - assertValidProps(this, this._currentElement.props);
431 - this._updateDOMProperties(prevElement.props, transaction);
448 + var lastProps = prevElement.props;
449 + var nextProps = this._currentElement.props;
450 +
451 + switch (this._tag) {
452 + case 'input':
453 + ReactDOMInput.updateWrapper(this);
454 + lastProps = ReactDOMInput.getNativeProps(this, lastProps);
455 + nextProps = ReactDOMInput.getNativeProps(this, nextProps);
456 + break;
457 + }
458 +
459 + assertValidProps(this, nextProps);
460 + this._updateDOMProperties(lastProps, nextProps, transaction);
461 this._updateDOMChildren(
433 - prevElement.props,
462 + lastProps,
463 + nextProps,
464 transaction,
465 processChildContext(context, this)
466 );
@@ -449,10 +479,10 @@ ReactDOMComponent.Mixin = {
479 *
480 * @private
481 * @param {object} lastProps
482 + * @param {object} nextProps
483 * @param {ReactReconcileTransaction} transaction
484 */
454 - _updateDOMProperties: function(lastProps, transaction) {
455 - var nextProps = this._currentElement.props;
485 + _updateDOMProperties: function(lastProps, nextProps, transaction) {
486 var propKey;
487 var styleName;
488 var styleUpdates;
@@ -558,11 +588,11 @@ ReactDOMComponent.Mixin = {
588 * children content.
589 *
590 * @param {object} lastProps
591 + * @param {object} nextProps
592 * @param {ReactReconcileTransaction} transaction
593 + * @param {object} context
594 */
563 - _updateDOMChildren: function(lastProps, transaction, context) {
564 - var nextProps = this._currentElement.props;
565 -
595 + _updateDOMChildren: function(lastProps, nextProps, transaction, context) {
596 var lastContent =
597 CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
598 var nextContent =
@@ -612,10 +642,17 @@ ReactDOMComponent.Mixin = {
642 * @internal
643 */
644 unmountComponent: function() {
645 + switch (this._tag) {
646 + case 'input':
647 + ReactDOMInput.unmountWrapper(this);
648 + break;
649 + }
650 +
651 this.unmountChildren();
652 ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID);
653 ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
654 this._rootNodeID = null;
655 + this._wrapperState = null;
656 }
657
658 };
src/renderers/dom/shared/ReactDefaultInjection.js
-2
@@ -29,7 +29,6 @@ var ReactDOMForm = require('ReactDOMForm');
29 var ReactDOMImg = require('ReactDOMImg');
30 var ReactDOMIDOperations = require('ReactDOMIDOperations');
31 var ReactDOMIframe = require('ReactDOMIframe');
32 -var ReactDOMInput = require('ReactDOMInput');
32 var ReactDOMOption = require('ReactDOMOption');
33 var ReactDOMSelect = require('ReactDOMSelect');
34 var ReactDOMTextarea = require('ReactDOMTextarea');
@@ -120,7 +119,6 @@ function inject() {
119 'form': ReactDOMForm,
120 'iframe': ReactDOMIframe,
121 'img': ReactDOMImg,
123 - 'input': ReactDOMInput,
122 'option': ReactDOMOption,
123 'select': ReactDOMSelect,
124 'textarea': ReactDOMTextarea,
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+7 -2
@@ -308,7 +308,8 @@ describe('ReactDOMComponent', function() {
308 genMarkup = function(props) {
309 var transaction = new ReactReconcileTransaction();
310 return (new NodeStub(props))._createOpenTagMarkupAndPutListeners(
311 - transaction
311 + transaction,
312 + props
313 );
314 };
315
@@ -357,7 +358,11 @@ describe('ReactDOMComponent', function() {
358
359 genMarkup = function(props) {
360 var transaction = new ReactReconcileTransaction();
360 - return (new NodeStub(props))._createContentMarkup(transaction, {});
361 + return (new NodeStub(props))._createContentMarkup(
362 + transaction,
363 + props,
364 + {}
365 + );
366 };
367
368 this.addMatchers({