Provide top level method for rendering subtree (passes context)
Jim committed
Apr 9, 2015 at 13:01 UTC
c93b4245cb51484193c5c21d0e8ca727b6025a47
6 files changed
+170
-15
src/addons/__tests__/renderSubtreeIntoContainer.js
new
+96
@@ -0,0 +1,96 @@
1
+/**
2
+ * Copyright 2013-2015, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
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
+ * @emails react-core
10
+ */
11
+
12
+'use strict';
13
+
14
+var React = require('React');
15
+var ReactTestUtils = require('ReactTestUtils');
16
+var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
17
+
18
+describe('renderSubtreeIntoContainer', function() {
19
+
20
+ it('should pass context when rendering subtree elsewhere', function () {
21
+
22
+ var portal = document.createElement('div');
23
+
24
+ var Component = React.createClass({
25
+ contextTypes: {
26
+ foo: React.PropTypes.string.isRequired
27
+ },
28
+
29
+ render: function() {
30
+ return <div>{this.context.foo}</div>;
31
+ }
32
+ });
33
+
34
+ var Parent = React.createClass({
35
+ childContextTypes: {
36
+ foo: React.PropTypes.string.isRequired,
37
+ },
38
+
39
+ getChildContext: function() {
40
+ return {
41
+ foo: 'bar'
42
+ };
43
+ },
44
+
45
+ render: function() {
46
+ return null;
47
+ },
48
+
49
+ componentDidMount: function() {
50
+ expect(function() {
51
+ renderSubtreeIntoContainer(this, <Component />, portal);
52
+ }.bind(this)).not.toThrow();
53
+ }
54
+ });
55
+
56
+ ReactTestUtils.renderIntoDocument(<Parent />);
57
+ expect(portal.firstChild.innerHTML).toBe('bar');
58
+ });
59
+
60
+ it('should throw if parentComponent is invalid', function () {
61
+ var portal = document.createElement('div');
62
+
63
+ var Component = React.createClass({
64
+ contextTypes: {
65
+ foo: React.PropTypes.string.isRequired
66
+ },
67
+
68
+ render: function() {
69
+ return <div>{this.context.foo}</div>;
70
+ }
71
+ });
72
+
73
+ var Parent = React.createClass({
74
+ childContextTypes: {
75
+ foo: React.PropTypes.string.isRequired,
76
+ },
77
+
78
+ getChildContext: function() {
79
+ return {
80
+ foo: 'bar'
81
+ };
82
+ },
83
+
84
+ render: function() {
85
+ return null;
86
+ },
87
+
88
+ componentDidMount: function() {
89
+ expect(function() {
90
+ renderSubtreeIntoContainer(<Parent />, <Component />, portal);
91
+ }).toThrow('Invariant Violation: parentComponent' +
92
+ 'must be a valid React Component');
93
+ }
94
+ });
95
+ });
96
+});
src/addons/renderSubtreeIntoContainer.js
new
+16
@@ -0,0 +1,16 @@
1
+/**
2
+ * Copyright 2013-2015, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
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 renderSubtreeIntoContainer
10
+*/
11
+
12
+'use strict';
13
+
14
+var ReactMount = require('ReactMount');
15
+
16
+module.exports = ReactMount.renderSubtreeIntoContainer;
src/browser/ReactWithAddons.js
+2
@@ -28,6 +28,7 @@ var ReactTransitionGroup = require('ReactTransitionGroup');
28
var ReactUpdates = require('ReactUpdates');
29
30
var cloneWithProps = require('cloneWithProps');
31
+var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
32
var shallowCompare = require('shallowCompare');
33
var update = require('update');
34
@@ -40,6 +41,7 @@ React.addons = {
41
batchedUpdates: ReactUpdates.batchedUpdates,
42
cloneWithProps: cloneWithProps,
43
createFragment: ReactFragment.create,
44
+ renderSubtreeIntoContainer: renderSubtreeIntoContainer,
45
shallowCompare: shallowCompare,
46
update: update
47
};
src/browser/ui/ReactMount.js
+51
-10
@@ -262,10 +262,12 @@ function mountComponentIntoNode(
262
rootID,
263
container,
264
transaction,
265
- shouldReuseMarkup) {
266
- var context = emptyObject;
265
+ shouldReuseMarkup,
266
+ context) {
267
if (__DEV__) {
268
- context = {};
268
+ if (context === emptyObject) {
269
+ context = {};
270
+ }
271
context[validateDOMNesting.tagStackContextKey] =
272
[container.nodeName.toLowerCase()];
273
}
@@ -288,7 +290,8 @@ function batchedMountComponentIntoNode(
290
componentInstance,
291
rootID,
292
container,
291
- shouldReuseMarkup) {
293
+ shouldReuseMarkup,
294
+ context) {
295
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
296
transaction.perform(
297
mountComponentIntoNode,
@@ -297,7 +300,8 @@ function batchedMountComponentIntoNode(
300
rootID,
301
container,
302
transaction,
300
- shouldReuseMarkup
303
+ shouldReuseMarkup,
304
+ context
305
);
306
ReactUpdates.ReactReconcileTransaction.release(transaction);
307
}
@@ -402,7 +406,8 @@ var ReactMount = {
406
_renderNewRootComponent: function(
407
nextElement,
408
container,
405
- shouldReuseMarkup
409
+ shouldReuseMarkup,
410
+ context
411
) {
412
// Various parts of our code (such as ReactCompositeComponent's
413
// _renderValidatedComponent) assume that calls to render aren't nested;
@@ -432,7 +437,8 @@ var ReactMount = {
437
componentInstance,
438
reactRootID,
439
container,
435
- shouldReuseMarkup
440
+ shouldReuseMarkup,
441
+ context
442
);
443
444
if (__DEV__) {
@@ -451,12 +457,26 @@ var ReactMount = {
457
* perform an update on it and only mutate the DOM as necessary to reflect the
458
* latest React component.
459
*
460
+ * @param {ReactComponent} parentComponent The conceptual parent of this render tree.
461
* @param {ReactElement} nextElement Component element to render.
462
* @param {DOMElement} container DOM element to render into.
463
* @param {?function} callback function triggered on completion
464
* @return {ReactComponent} Component instance rendered in `container`.
465
*/
459
- render: function(nextElement, container, callback) {
466
+ renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
467
+ invariant(
468
+ parentComponent != null && parentComponent._reactInternalInstance != null,
469
+ 'parentComponent must be a valid React Component'
470
+ );
471
+ return ReactMount._renderSubtreeIntoContainer(
472
+ parentComponent,
473
+ nextElement,
474
+ container,
475
+ callback
476
+ );
477
+ },
478
+
479
+ _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
480
invariant(
481
ReactElement.isValidElement(nextElement),
482
'React.render(): Invalid component element.%s',
@@ -524,11 +544,15 @@ var ReactMount = {
544
}
545
546
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;
527
-
547
var component = ReactMount._renderNewRootComponent(
548
nextElement,
549
container,
531
- shouldReuseMarkup
550
+ shouldReuseMarkup,
551
+ parentComponent != null ?
552
+ parentComponent._reactInternalInstance._processChildContext(
553
+ parentComponent._reactInternalInstance._context
554
+ ) :
555
+ emptyObject
556
).getPublicInstance();
557
if (callback) {
558
callback.call(component);
@@ -536,6 +560,23 @@ var ReactMount = {
560
return component;
561
},
562
563
+
564
+ /**
565
+ * Renders a React component into the DOM in the supplied `container`.
566
+ *
567
+ * If the React component was previously rendered into `container`, this will
568
+ * perform an update on it and only mutate the DOM as necessary to reflect the
569
+ * latest React component.
570
+ *
571
+ * @param {ReactElement} nextElement Component element to render.
572
+ * @param {DOMElement} container DOM element to render into.
573
+ * @param {?function} callback function triggered on completion
574
+ * @return {ReactComponent} Component instance rendered in `container`.
575
+ */
576
+ render: function(nextElement, container, callback) {
577
+ return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
578
+ },
579
+
580
/**
581
* Constructs a component instance of `constructor` with `initialProps` and
582
* renders it into the supplied `container`.
src/core/ReactDefaultBatchingStrategy.js
+3
-3
@@ -54,16 +54,16 @@ var ReactDefaultBatchingStrategy = {
54
* Call the provided function in a context within which calls to `setState`
55
* and friends are batched such that components aren't updated unnecessarily.
56
*/
57
- batchedUpdates: function(callback, a, b, c, d) {
57
+ batchedUpdates: function(callback, a, b, c, d, e) {
58
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
59
60
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
61
62
// The code is written this way to avoid extra allocations
63
if (alreadyBatchingUpdates) {
64
- callback(a, b, c, d);
64
+ callback(a, b, c, d, e);
65
} else {
66
- transaction.perform(callback, null, a, b, c, d);
66
+ transaction.perform(callback, null, a, b, c, d, e);
67
}
68
}
69
};
src/core/ReactUpdates.js
+2
-2
@@ -105,9 +105,9 @@ assign(
105
106
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
107
108
-function batchedUpdates(callback, a, b, c, d) {
108
+function batchedUpdates(callback, a, b, c, d, e) {
109
ensureInjected();
110
- batchingStrategy.batchedUpdates(callback, a, b, c, d);
110
+ batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
111
}
112
113
/**