Added warning if owner-based and parent-based contexts differ.
Jim committed
Nov 17, 2014 at 17:02 UTC
081feeb2dd97ae0854a7d2448232e651c7330344
15 files changed
+254
-93
src/browser/server/ReactServerRendering.js
+9
-4
@@ -17,14 +17,17 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum');
17
var ReactServerRenderingTransaction =
18
require('ReactServerRenderingTransaction');
19
20
+var emptyObject = require('emptyObject');
21
var instantiateReactComponent = require('instantiateReactComponent');
22
var invariant = require('invariant');
23
24
/**
25
* @param {ReactElement} element
26
+ * @param {?object} context
27
* @return {string} the HTML markup
28
*/
27
-function renderToString(element) {
29
+function renderToString(element, context) {
30
+ if(context === undefined) context = emptyObject;
31
invariant(
32
ReactElement.isValidElement(element),
33
'renderToString(): You must pass a valid ReactElement.'
@@ -37,7 +40,7 @@ function renderToString(element) {
40
41
return transaction.perform(function() {
42
var componentInstance = instantiateReactComponent(element, null);
40
- var markup = componentInstance.mountComponent(id, transaction, 0);
43
+ var markup = componentInstance.mountComponent(id, transaction, 0, context);
44
return ReactMarkupChecksum.addChecksumToMarkup(markup);
45
}, null);
46
} finally {
@@ -47,10 +50,12 @@ function renderToString(element) {
50
51
/**
52
* @param {ReactElement} element
53
+ * @param {?object} context
54
* @return {string} the HTML markup, without the extra React ID and checksum
55
* (for generating static pages)
56
*/
53
-function renderToStaticMarkup(element) {
57
+function renderToStaticMarkup(element, context) {
58
+ if(context === undefined) context = emptyObject;
59
invariant(
60
ReactElement.isValidElement(element),
61
'renderToStaticMarkup(): You must pass a valid ReactElement.'
@@ -63,7 +68,7 @@ function renderToStaticMarkup(element) {
68
69
return transaction.perform(function() {
70
var componentInstance = instantiateReactComponent(element, null);
66
- return componentInstance.mountComponent(id, transaction, 0);
71
+ return componentInstance.mountComponent(id, transaction, 0, context);
72
}, null);
73
} finally {
74
ReactServerRenderingTransaction.release(transaction);
src/browser/ui/ReactDOMComponent.js
+22
-13
@@ -168,19 +168,21 @@ ReactDOMComponent.Mixin = {
168
mountComponent: ReactPerf.measure(
169
'ReactDOMComponent',
170
'mountComponent',
171
- function(rootID, transaction, mountDepth) {
171
+ function(rootID, transaction, mountDepth, context) {
172
+ invariant(context !== undefined, "Context is required parameter");
173
ReactComponent.Mixin.mountComponent.call(
174
this,
175
rootID,
176
transaction,
176
- mountDepth
177
+ mountDepth,
178
+ context
179
);
180
this._previousStyleCopy = null;
181
assertValidProps(this._currentElement.props);
182
var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
183
return (
184
this._createOpenTagMarkupAndPutListeners(transaction) +
183
- this._createContentMarkup(transaction) +
185
+ this._createContentMarkup(transaction, context) +
186
closeTag
187
);
188
}
@@ -242,9 +244,10 @@ ReactDOMComponent.Mixin = {
244
*
245
* @private
246
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
247
+ * @param {object} context
248
* @return {string} Content markup.
249
*/
247
- _createContentMarkup: function(transaction) {
250
+ _createContentMarkup: function(transaction, context) {
251
var prefix = '';
252
if (this._tag === 'listing' ||
253
this._tag === 'pre' ||
@@ -272,7 +275,8 @@ ReactDOMComponent.Mixin = {
275
} else if (childrenToUse != null) {
276
var mountImages = this.mountChildren(
277
childrenToUse,
275
- transaction
278
+ transaction,
279
+ context
280
);
281
return prefix + mountImages.join('');
282
}
@@ -280,7 +284,8 @@ ReactDOMComponent.Mixin = {
284
return prefix;
285
},
286
283
- receiveComponent: function(nextElement, transaction) {
287
+ receiveComponent: function(nextElement, transaction, context) {
288
+ invariant(context !== undefined, "Context is required parameter");
289
if (nextElement === this._currentElement &&
290
nextElement._owner != null) {
291
// Since elements are immutable after the owner is rendered,
@@ -295,7 +300,7 @@ ReactDOMComponent.Mixin = {
300
301
var prevElement = this._currentElement;
302
this._currentElement = nextElement;
298
- this.updateComponent(transaction, prevElement, nextElement);
303
+ this.updateComponent(transaction, prevElement, nextElement, context);
304
},
305
306
/**
@@ -311,16 +316,19 @@ ReactDOMComponent.Mixin = {
316
updateComponent: ReactPerf.measure(
317
'ReactDOMComponent',
318
'updateComponent',
314
- function(transaction, prevElement, nextElement) {
319
+ function(transaction, prevElement, nextElement, context) {
320
+ if(context === undefined) throw new Error("Context required for mounting");
321
+ if(context === null) throw new Error("Assert: context is not null");
322
assertValidProps(this._currentElement.props);
323
ReactComponent.Mixin.updateComponent.call(
324
this,
325
transaction,
326
prevElement,
320
- nextElement
327
+ nextElement,
328
+ context
329
);
330
this._updateDOMProperties(prevElement.props, transaction);
323
- this._updateDOMChildren(prevElement.props, transaction);
331
+ this._updateDOMChildren(prevElement.props, transaction, context);
332
}
333
),
334
@@ -428,7 +436,8 @@ ReactDOMComponent.Mixin = {
436
* @param {object} lastProps
437
* @param {ReactReconcileTransaction} transaction
438
*/
431
- _updateDOMChildren: function(lastProps, transaction) {
439
+ _updateDOMChildren: function(lastProps, transaction, context) {
440
+ invariant(context !== undefined, "Context is required parameter");
441
var nextProps = this._currentElement.props;
442
443
var lastContent =
@@ -452,7 +461,7 @@ ReactDOMComponent.Mixin = {
461
var lastHasContentOrHtml = lastContent != null || lastHtml != null;
462
var nextHasContentOrHtml = nextContent != null || nextHtml != null;
463
if (lastChildren != null && nextChildren == null) {
455
- this.updateChildren(null, transaction);
464
+ this.updateChildren(null, transaction, context);
465
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
466
this.updateTextContent('');
467
}
@@ -469,7 +478,7 @@ ReactDOMComponent.Mixin = {
478
);
479
}
480
} else if (nextChildren != null) {
472
- this.updateChildren(nextChildren, transaction);
481
+ this.updateChildren(nextChildren, transaction, context);
482
}
483
},
484
src/browser/ui/ReactDOMTextComponent.js
+1
-1
@@ -66,7 +66,7 @@ assign(ReactDOMTextComponent.prototype, {
66
* @return {string} Markup for this text node.
67
* @internal
68
*/
69
- mountComponent: function(rootID, transaction, mountDepth) {
69
+ mountComponent: function(rootID, transaction, mountDepth, context) {
70
this._rootNodeID = rootID;
71
var escapedText = escapeTextForBrowser(this._stringText);
72
src/browser/ui/ReactMount.js
+2
-1
@@ -22,6 +22,7 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum');
22
var ReactPerf = require('ReactPerf');
23
var ReactUpdates = require('ReactUpdates');
24
25
+var emptyObject = require('emptyObject');
26
var containsNode = require('containsNode');
27
var deprecated = require('deprecated');
28
var getReactRootElementInContainer = require('getReactRootElementInContainer');
@@ -224,7 +225,7 @@ function mountComponentIntoNode(
225
container,
226
transaction,
227
shouldReuseMarkup) {
227
- var markup = this.mountComponent(rootID, transaction, 0);
228
+ var markup = this.mountComponent(rootID, transaction, 0, emptyObject);
229
ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
230
}
231
src/browser/ui/__tests__/ReactDOMComponent-test.js
+2
-2
@@ -283,7 +283,7 @@ describe('ReactDOMComponent', function() {
283
284
genMarkup = function(props) {
285
var transaction = new ReactReconcileTransaction();
286
- return (new NodeStub(props))._createContentMarkup(transaction);
286
+ return (new NodeStub(props))._createContentMarkup(transaction, {});
287
};
288
289
this.addMatchers({
@@ -328,7 +328,7 @@ describe('ReactDOMComponent', function() {
328
_owner: null,
329
_context: null
330
});
331
- return stubComponent.mountComponent('test', transaction, 0);
331
+ return stubComponent.mountComponent('test', transaction, 0, {});
332
};
333
});
334
src/browser/ui/dom/__tests__/Danger-test.js
+3
-3
@@ -32,7 +32,7 @@ describe('Danger', function() {
32
it('should render markup', function() {
33
var markup = instantiateReactComponent(
34
<div />
35
- ).mountComponent('.rX', transaction, 0);
35
+ ).mountComponent('.rX', transaction, 0, {});
36
var output = Danger.dangerouslyRenderMarkup([markup])[0];
37
38
expect(output.nodeName).toBe('DIV');
@@ -44,7 +44,7 @@ describe('Danger', function() {
44
).mountComponent(
45
'.rX',
46
transaction,
47
- 0
47
+ 0, {}
48
);
49
var output = Danger.dangerouslyRenderMarkup([markup])[0];
50
@@ -55,7 +55,7 @@ describe('Danger', function() {
55
it('should render wrapped markup', function() {
56
var markup = instantiateReactComponent(
57
<th />
58
- ).mountComponent('.rX', transaction, 0);
58
+ ).mountComponent('.rX', transaction, 0, {});
59
var output = Danger.dangerouslyRenderMarkup([markup])[0];
60
61
expect(output.nodeName).toBe('TH');
src/core/ReactComponent.js
+4
-3
@@ -114,7 +114,6 @@ var ReactComponent = {
114
// We keep the old element and a reference to the pending element
115
// to track updates.
116
this._currentElement = element;
117
-
117
this._rootNodeID = null;
118
this._mountIndex = 0;
119
this._mountDepth = 0;
@@ -134,7 +133,8 @@ var ReactComponent = {
133
* @return {?string} Rendered markup to be inserted into the DOM.
134
* @internal
135
*/
137
- mountComponent: function(rootID, transaction, mountDepth) {
136
+ mountComponent: function(rootID, transaction, mountDepth, context) {
137
+ invariant(context !== undefined, "Context is required parameter");
138
var ref = this._currentElement.ref;
139
if (ref != null) {
140
var owner = this._currentElement._owner;
@@ -173,7 +173,8 @@ var ReactComponent = {
173
* @param {object} nextElement
174
* @internal
175
*/
176
- updateComponent: function(transaction, prevElement, nextElement) {
176
+ updateComponent: function(transaction, prevElement, nextElement, context) {
177
+ invariant(context !== undefined, "Context is required parameter");
178
// If either the owner or a `ref` has changed, make sure the newest owner
179
// has stored a reference to `this`, and the previous owner (if different)
180
// has forgotten the reference to `this`. We use the element instead
src/core/ReactCompositeComponent.js
+95
-27
@@ -20,10 +20,12 @@ var ReactPerf = require('ReactPerf');
20
var ReactPropTypeLocations = require('ReactPropTypeLocations');
21
var ReactUpdates = require('ReactUpdates');
22
23
+var emptyObject = require('emptyObject');
24
var assign = require('Object.assign');
25
var emptyObject = require('emptyObject');
26
var invariant = require('invariant');
27
var keyMirror = require('keyMirror');
28
+var monitorCodeUse = require('monitorCodeUse');
29
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
30
var warning = require('warning');
31
@@ -116,6 +118,7 @@ var ReactCompositeComponentMixin = assign({},
118
this._instance.refs = emptyObject;
119
120
this._pendingElement = null;
121
+ this._pendingContext = null;
122
this._pendingState = null;
123
this._pendingForceUpdate = false;
124
this._compositeLifeCycleState = null;
@@ -125,6 +128,8 @@ var ReactCompositeComponentMixin = assign({},
128
// Children can be either an array or more than one argument
129
ReactComponent.Mixin.construct.apply(this, arguments);
130
131
+ this._context = null;
132
+
133
// See ReactUpdates.
134
this._pendingCallbacks = null;
135
},
@@ -152,14 +157,18 @@ var ReactCompositeComponentMixin = assign({},
157
mountComponent: ReactPerf.measure(
158
'ReactCompositeComponent',
159
'mountComponent',
155
- function(rootID, transaction, mountDepth) {
160
+ function(rootID, transaction, mountDepth, context) {
161
+ invariant(context !== undefined, "Context is required parameter");
162
ReactComponent.Mixin.mountComponent.call(
163
this,
164
rootID,
165
transaction,
160
- mountDepth
166
+ mountDepth,
167
+ context
168
);
169
170
+ this._context = context;
171
+
172
var inst = this._instance;
173
174
// Store a reference from the instance back to the internal representation
@@ -168,6 +177,9 @@ var ReactCompositeComponentMixin = assign({},
177
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
178
179
inst.context = this._processContext(this._currentElement._context);
180
+ if (__DEV__) {
181
+ this._warnIfContextsDiffer(this._currentElement._context, context);
182
+ }
183
inst.props = this._processProps(this._currentElement.props);
184
185
var initialState = inst.getInitialState ? inst.getInitialState() : null;
@@ -211,7 +223,8 @@ var ReactCompositeComponentMixin = assign({},
223
var markup = this._renderedComponent.mountComponent(
224
rootID,
225
transaction,
214
- mountDepth + 1
226
+ mountDepth + 1,
227
+ this._processChildContext(context)
228
);
229
if (inst.componentDidMount) {
230
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
@@ -246,6 +259,8 @@ var ReactCompositeComponentMixin = assign({},
259
260
ReactComponent.Mixin.unmountComponent.call(this);
261
262
+ this._context = null;
263
+
264
// Delete the reference from the instance to this internal representation
265
// which allow the internals to be properly cleaned up even if the user
266
// leaks a reference to the public instance.
@@ -403,18 +418,17 @@ var ReactCompositeComponentMixin = assign({},
418
_processContext: function(context) {
419
var maskedContext = null;
420
var contextTypes = this._instance.constructor.contextTypes;
406
- if (contextTypes) {
407
- maskedContext = {};
408
- for (var contextName in contextTypes) {
409
- maskedContext[contextName] = context[contextName];
410
- }
411
- if (__DEV__) {
412
- this._checkPropTypes(
413
- contextTypes,
414
- maskedContext,
415
- ReactPropTypeLocations.context
416
- );
417
- }
421
+ if (!contextTypes) return emptyObject;
422
+ maskedContext = {};
423
+ for (var contextName in contextTypes) {
424
+ maskedContext[contextName] = context[contextName];
425
+ }
426
+ if (__DEV__) {
427
+ this._checkPropTypes(
428
+ contextTypes,
429
+ maskedContext,
430
+ ReactPropTypeLocations.context
431
+ );
432
}
433
return maskedContext;
434
},
@@ -502,7 +516,8 @@ var ReactCompositeComponentMixin = assign({},
516
}
517
},
518
505
- receiveComponent: function(nextElement, transaction) {
519
+ receiveComponent: function(nextElement, transaction, context) {
520
+ invariant(context !== undefined, "Context is required parameter");
521
if (nextElement === this._currentElement &&
522
nextElement._owner != null) {
523
// Since elements are immutable after the owner is rendered,
@@ -516,6 +531,7 @@ var ReactCompositeComponentMixin = assign({},
531
}
532
533
this._pendingElement = nextElement;
534
+ this._pendingContext = context;
535
this.performUpdateIfNecessary(transaction);
536
},
537
@@ -537,6 +553,7 @@ var ReactCompositeComponentMixin = assign({},
553
554
if (this._pendingElement == null &&
555
this._pendingState == null &&
556
+ this._pendingContext == null &&
557
!this._pendingForceUpdate) {
558
return;
559
}
@@ -548,13 +565,46 @@ var ReactCompositeComponentMixin = assign({},
565
this._pendingElement = null;
566
}
567
568
+ var prevContext = this._context;
569
+ var nextContext = prevContext;
570
+ if (this._pendingContext != null) {
571
+ nextContext = this._pendingContext;
572
+ this._pendingContext = null;
573
+ }
574
+
575
this.updateComponent(
576
transaction,
577
prevElement,
554
- nextElement
578
+ nextElement,
579
+ prevContext,
580
+ nextContext
581
);
582
},
583
584
+ /**
585
+ * Compare two contexts, warning if they are different
586
+ * TODO: Remove this check when owner-context is removed
587
+ */
588
+ _warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) {
589
+ invariant(ownerBasedContext !== undefined, "Owner based context is required parameter");
590
+ invariant(parentBasedContext !== undefined, "Parent based ontext is required parameter");
591
+ var ownerKeys = Object.keys(ownerBasedContext).sort();
592
+ var parentKeys = Object.keys(parentBasedContext).sort();
593
+ if (ownerKeys.length != parentKeys.length || ownerKeys.toString() != parentKeys.toString()) {
594
+ var message = ("owner based context (keys: " +
595
+ Object.keys(ownerBasedContext) + ") does not equal parent based" +
596
+ " context (keys: "+Object.keys(parentBasedContext)+")" +
597
+ " while mounting " +
598
+ (this._instance.constructor.displayName || 'ReactCompositeComponent'));
599
+ console.warn(message);
600
+ monitorCodeUse('contexts_differ', {
601
+ ownerBasedKeys: Object.keys(ownerBasedContext),
602
+ parentBasedKeys: Object.keys(parentBasedContext),
603
+ mounting: (this._instance.constructor.displayName || 'ReactCompositeComponent')
604
+ });
605
+ }
606
+ },
607
+
608
/**
609
* Perform an update to a mounted component. The componentWillReceiveProps and
610
* shouldComponentUpdate methods are called, then (assuming the update isn't
@@ -573,13 +623,20 @@ var ReactCompositeComponentMixin = assign({},
623
updateComponent: ReactPerf.measure(
624
'ReactCompositeComponent',
625
'updateComponent',
576
- function(transaction, prevParentElement, nextParentElement) {
626
+ function(
627
+ transaction,
628
+ prevParentElement,
629
+ nextParentElement,
630
+ prevUnmaskedContext,
631
+ nextUnmaskedContext) {
632
// Update refs regardless of what shouldComponentUpdate returns
633
ReactComponent.Mixin.updateComponent.call(
634
this,
635
transaction,
636
prevParentElement,
582
- nextParentElement
637
+ nextParentElement,
638
+ prevUnmaskedContext,
639
+ nextUnmaskedContext
640
);
641
642
var inst = this._instance;
@@ -623,6 +680,7 @@ var ReactCompositeComponentMixin = assign({},
680
// If it's determined that a component should not update, we still want
681
// to set props and state but we shortcut the rest of the update.
682
this._currentElement = nextParentElement;
683
+ this._context = nextUnmaskedContext;
684
inst.props = nextProps;
685
inst.state = nextState;
686
inst.context = nextContext;
@@ -636,7 +694,8 @@ var ReactCompositeComponentMixin = assign({},
694
nextProps,
695
nextState,
696
nextContext,
639
- transaction
697
+ transaction,
698
+ nextUnmaskedContext
699
);
700
}),
701
@@ -649,6 +708,7 @@ var ReactCompositeComponentMixin = assign({},
708
* @param {?object} nextState Next object to set as state.
709
* @param {?object} nextContext Next public object to set as context.
710
* @param {ReactReconcileTransaction} transaction
711
+ * @param {?object} unmaskedContext
712
* @private
713
*/
714
_performComponentUpdate: function(
@@ -656,8 +716,11 @@ var ReactCompositeComponentMixin = assign({},
716
nextProps,
717
nextState,
718
nextContext,
659
- transaction
719
+ transaction,
720
+ unmaskedContext
721
) {
722
+ invariant(unmaskedContext !== undefined, "Context required for mounting");
723
+ invariant(unmaskedContext !== null, "Context must be non-null");
724
var inst = this._instance;
725
726
var prevProps = inst.props;
@@ -669,11 +732,12 @@ var ReactCompositeComponentMixin = assign({},
732
}
733
734
this._currentElement = nextElement;
735
+ this._context = unmaskedContext;
736
inst.props = nextProps;
737
inst.state = nextState;
738
inst.context = nextContext;
739
676
- this._updateRenderedComponent(transaction);
740
+ this._updateRenderedComponent(transaction, nextContext);
741
742
if (inst.componentDidUpdate) {
743
transaction.getReactMountReady().enqueue(
@@ -689,14 +753,16 @@ var ReactCompositeComponentMixin = assign({},
753
* @param {ReactReconcileTransaction} transaction
754
* @internal
755
*/
692
- _updateRenderedComponent: function(transaction) {
756
+ _updateRenderedComponent: function(transaction, context) {
757
+ invariant(context !== undefined, "Context is required parameter");
758
var prevComponentInstance = this._renderedComponent;
759
var prevRenderedElement = prevComponentInstance._currentElement;
760
var nextRenderedElement = this._renderValidatedComponent();
761
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
762
prevComponentInstance.receiveComponent(
763
nextRenderedElement,
699
- transaction
764
+ transaction,
765
+ this._processChildContext(context)
766
);
767
} else {
768
// These two IDs are actually the same! But nothing should rely on that.
@@ -711,7 +777,8 @@ var ReactCompositeComponentMixin = assign({},
777
var nextMarkup = this._renderedComponent.mountComponent(
778
thisID,
779
transaction,
714
- this._mountDepth + 1
780
+ this._mountDepth + 1,
781
+ context
782
);
783
ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID(
784
prevComponentID,
@@ -828,12 +895,13 @@ var ShallowMixin = assign({},
895
* @final
896
* @internal
897
*/
831
- mountComponent: function(rootID, transaction, mountDepth) {
898
+ mountComponent: function(rootID, transaction, mountDepth, context) {
899
ReactComponent.Mixin.mountComponent.call(
900
this,
901
rootID,
902
transaction,
836
- mountDepth
903
+ mountDepth,
904
+ context
905
);
906
907
var inst = this._instance;
src/core/ReactContext.js
+2
-1
@@ -12,6 +12,7 @@
12
"use strict";
13
14
var assign = require('Object.assign');
15
+var emptyObject = require('emptyObject');
16
17
/**
18
* Keeps track of the current context.
@@ -25,7 +26,7 @@ var ReactContext = {
26
* @internal
27
* @type {object}
28
*/
28
- current: {},
29
+ current: emptyObject,
30
31
/**
32
* Temporarily extends the current context while executing scopedCallback.
src/core/ReactMultiChild.js
+21
-9
@@ -17,6 +17,7 @@ var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
17
18
var flattenChildren = require('flattenChildren');
19
var instantiateReactComponent = require('instantiateReactComponent');
20
+var invariant = require('invariant');
21
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
22
23
/**
@@ -178,7 +179,8 @@ var ReactMultiChild = {
179
* @return {array} An array of mounted representations.
180
* @internal
181
*/
181
- mountChildren: function(nestedChildren, transaction) {
182
+ mountChildren: function(nestedChildren, transaction, context) {
183
+ invariant(context !== undefined, "Context is required parameter");
184
var children = flattenChildren(nestedChildren);
185
var mountImages = [];
186
var index = 0;
@@ -195,7 +197,8 @@ var ReactMultiChild = {
197
var mountImage = childInstance.mountComponent(
198
rootID,
199
transaction,
198
- this._mountDepth + 1
200
+ this._mountDepth + 1,
201
+ context
202
);
203
childInstance._mountIndex = index;
204
mountImages.push(mountImage);
@@ -240,11 +243,12 @@ var ReactMultiChild = {
243
* @param {ReactReconcileTransaction} transaction
244
* @internal
245
*/
243
- updateChildren: function(nextNestedChildren, transaction) {
246
+ updateChildren: function(nextNestedChildren, transaction, context) {
247
+ invariant(context !== undefined, "Context is required parameter");
248
updateDepth++;
249
var errorThrown = true;
250
try {
247
- this._updateChildren(nextNestedChildren, transaction);
251
+ this._updateChildren(nextNestedChildren, transaction, context);
252
errorThrown = false;
253
} finally {
254
updateDepth--;
@@ -263,7 +267,8 @@ var ReactMultiChild = {
267
* @final
268
* @protected
269
*/
266
- _updateChildren: function(nextNestedChildren, transaction) {
270
+ _updateChildren: function(nextNestedChildren, transaction, context) {
271
+ invariant(context !== undefined, "Context is required parameter");
272
var nextChildren = flattenChildren(nextNestedChildren);
273
var prevChildren = this._renderedChildren;
274
if (!nextChildren && !prevChildren) {
@@ -284,7 +289,7 @@ var ReactMultiChild = {
289
if (shouldUpdateReactComponent(prevElement, nextElement)) {
290
this.moveChild(prevChild, nextIndex, lastIndex);
291
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
287
- prevChild.receiveComponent(nextElement, transaction);
292
+ prevChild.receiveComponent(nextElement, transaction, context);
293
prevChild._mountIndex = nextIndex;
294
} else {
295
if (prevChild) {
@@ -298,7 +303,7 @@ var ReactMultiChild = {
303
null
304
);
305
this._mountChildByNameAtIndex(
301
- nextChildInstance, name, nextIndex, transaction
306
+ nextChildInstance, name, nextIndex, transaction, context
307
);
308
}
309
nextIndex++;
@@ -389,13 +394,20 @@ var ReactMultiChild = {
394
* @param {ReactReconcileTransaction} transaction
395
* @private
396
*/
392
- _mountChildByNameAtIndex: function(child, name, index, transaction) {
397
+ _mountChildByNameAtIndex: function(
398
+ child,
399
+ name,
400
+ index,
401
+ transaction,
402
+ context) {
403
+ invariant(context !== undefined, "Context is required parameter");
404
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
405
var rootID = this._rootNodeID + name;
406
var mountImage = child.mountComponent(
407
rootID,
408
transaction,
398
- this._mountDepth + 1
409
+ this._mountDepth + 1,
410
+ context
411
);
412
child._mountIndex = index;
413
this.createChild(child, mountImage);
src/core/ReactUpdates.js
+1
@@ -140,6 +140,7 @@ function runBatchedUpdates(transaction) {
140
// be here, but we assume that it has cleared its _pendingCallbacks and
141
// that performUpdateIfNecessary is a noop.
142
var component = dirtyComponents[i];
143
+
144
// If performUpdateIfNecessary happens to enqueue any new updates, we
145
// shouldn't execute the callbacks until the next render happens, so
146
// stash the callbacks first
src/core/__tests__/ReactCompositeComponent-test.js
+83
-14
@@ -961,6 +961,29 @@ describe('ReactCompositeComponent', function() {
961
reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
962
});
963
964
+ it('warn if contexts differ', function() {
965
+ var Component = React.createClass({
966
+ contextTypes: {
967
+ foo: ReactPropTypes.string.isRequired
968
+ },
969
+
970
+ render: function() {
971
+ return <div />;
972
+ }
973
+ });
974
+
975
+ React.withContext({foo: 'bar'}, function() {
976
+ ReactTestUtils.renderIntoDocument(<Component />);
977
+ });
978
+
979
+ expect(console.warn.mock.calls.length).toBe(2);
980
+ expect(console.warn.mock.calls[1][0]).toBe(
981
+ 'owner based context (keys: foo) does not equal parent based ' +
982
+ 'context (keys: ) while mounting ReactCompositeComponent'
983
+ );
984
+
985
+ });
986
+
987
it('should check context types', function() {
988
var Component = React.createClass({
989
contextTypes: {
@@ -979,21 +1002,50 @@ describe('ReactCompositeComponent', function() {
1002
'Warning: Required context `foo` was not specified in `Component`.'
1003
);
1004
982
- React.withContext({foo: 'bar'}, function() {
983
- ReactTestUtils.renderIntoDocument(<Component />);
1005
+ var ComponentInFooStringContext = React.createClass({
1006
+ childContextTypes: {
1007
+ foo: ReactPropTypes.string
1008
+ },
1009
+
1010
+ getChildContext: function() {
1011
+ return {
1012
+ foo: this.props.fooValue
1013
+ };
1014
+ },
1015
+
1016
+ render: function() {
1017
+ return <Component />;
1018
+ }
1019
});
1020
1021
+ ReactTestUtils.renderIntoDocument(<ComponentInFooStringContext fooValue={'bar'} />);
1022
+
1023
// Previous call should not error
1024
expect(console.warn.mock.calls.length).toBe(1);
1025
989
- React.withContext({foo: 123}, function() {
990
- ReactTestUtils.renderIntoDocument(<Component />);
1026
+ var ComponentInFooNumberContext = React.createClass({
1027
+ childContextTypes: {
1028
+ foo: ReactPropTypes.number
1029
+ },
1030
+
1031
+ getChildContext: function() {
1032
+ return {
1033
+ foo: this.props.fooValue
1034
+ };
1035
+ },
1036
+
1037
+ render: function() {
1038
+ return <Component />;
1039
+ }
1040
});
1041
1042
+ ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
1043
+
1044
expect(console.warn.mock.calls.length).toBe(2);
1045
expect(console.warn.mock.calls[1][0]).toBe(
1046
'Warning: Invalid context `foo` of type `number` supplied ' +
996
- 'to `Component`, expected `string`.'
1047
+ 'to `Component`, expected `string`.' +
1048
+ ' Check the render method of `ComponentInFooNumberContext`.'
1049
);
1050
});
1051
@@ -1014,16 +1066,18 @@ describe('ReactCompositeComponent', function() {
1066
});
1067
1068
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
1017
-
1018
- expect(console.warn.mock.calls.length).toBe(1);
1069
+ expect(console.warn.mock.calls.length).toBe(2);
1070
expect(console.warn.mock.calls[0][0]).toBe(
1071
'Warning: Required child context `foo` was not specified in `Component`.'
1072
);
1073
+ expect(console.warn.mock.calls[1][0]).toBe(
1074
+ 'Warning: Required child context `foo` was not specified in `Component`.'
1075
+ );
1076
1077
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
1078
1025
- expect(console.warn.mock.calls.length).toBe(2);
1026
- expect(console.warn.mock.calls[1][0]).toBe(
1079
+ expect(console.warn.mock.calls.length).toBe(4);
1080
+ expect(console.warn.mock.calls[3][0]).toBe(
1081
'Warning: Invalid child context `foo` of type `number` ' +
1082
'supplied to `Component`, expected `string`.'
1083
);
@@ -1037,7 +1091,7 @@ describe('ReactCompositeComponent', function() {
1091
);
1092
1093
// Previous calls should not log errors
1040
- expect(console.warn.mock.calls.length).toBe(2);
1094
+ expect(console.warn.mock.calls.length).toBe(4);
1095
});
1096
1097
it('should filter out context not in contextTypes', function() {
@@ -1051,11 +1105,26 @@ describe('ReactCompositeComponent', function() {
1105
}
1106
});
1107
1054
- var instance = React.withContext({foo: 'abc', bar: 123}, function() {
1055
- return <Component />;
1108
+ var ComponentInFooBarContext = React.createClass({
1109
+ childContextTypes: {
1110
+ foo: ReactPropTypes.string,
1111
+ bar: ReactPropTypes.number
1112
+ },
1113
+
1114
+ getChildContext: function() {
1115
+ return {
1116
+ foo: 'abc',
1117
+ bar: 123
1118
+ };
1119
+ },
1120
+
1121
+ render: function() {
1122
+ return <Component />;
1123
+ }
1124
});
1057
- instance = ReactTestUtils.renderIntoDocument(instance);
1058
- reactComponentExpect(instance).scalarContextEqual({foo: 'abc'});
1125
+
1126
+ var instance = ReactTestUtils.renderIntoDocument(<ComponentInFooBarContext />);
1127
+ reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'});
1128
});
1129
1130
it('should filter context properly in callbacks', function() {
src/core/__tests__/ReactElement-test.js
+1
-7
@@ -81,7 +81,7 @@ describe('ReactElement', function() {
81
expect(element.props).toEqual({foo:'56'});
82
});
83
84
- it('preserves the context on the element', function() {
84
+ it('preserves the legacy context on the element', function() {
85
var Component = React.createFactory(ComponentClass);
86
var element;
87
@@ -108,12 +108,6 @@ describe('ReactElement', function() {
108
var element;
109
110
var Wrapper = React.createClass({
111
- childContextTypes: {
112
- foo: React.PropTypes.string
113
- },
114
- getChildContext: function() {
115
- return { foo: 'bar' };
116
- },
111
render: function() {
112
element = Component();
113
return element;
src/test/ReactTestUtils.js
+5
-5
@@ -328,23 +328,23 @@ assign(
328
ReactCompositeComponent.ShallowMixin
329
);
330
331
-ReactShallowRenderer.prototype.render = function(element) {
331
+ReactShallowRenderer.prototype.render = function(element, context) {
332
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
333
- this._render(element, transaction);
333
+ this._render(element, transaction, context);
334
ReactUpdates.ReactReconcileTransaction.release(transaction);
335
};
336
337
-ReactShallowRenderer.prototype._render = function(element, transaction) {
337
+ReactShallowRenderer.prototype._render = function(element, transaction, context) {
338
if (!this._instance) {
339
var rootID = ReactInstanceHandles.createReactRootID();
340
var instance = new ShallowComponentWrapper(new element.type(element.props));
341
instance.construct(element);
342
343
- instance.mountComponent(rootID, transaction, 0);
343
+ instance.mountComponent(rootID, transaction, 0, context);
344
345
this._instance = instance;
346
} else {
347
- this._instance.receiveComponent(element, transaction);
347
+ this._instance.receiveComponent(element, transaction, context);
348
}
349
};
350
src/test/__tests__/ReactTestUtils-test.js
+3
-3
@@ -46,7 +46,7 @@ describe('ReactTestUtils', function() {
46
});
47
48
var shallowRenderer = ReactTestUtils.createRenderer();
49
- shallowRenderer.render(<SomeComponent />);
49
+ shallowRenderer.render(<SomeComponent />, {});
50
51
var result = shallowRenderer.getRenderOutput();
52
@@ -91,7 +91,7 @@ describe('ReactTestUtils', function() {
91
});
92
93
var shallowRenderer = ReactTestUtils.createRenderer();
94
- shallowRenderer.render(<SomeComponent />);
94
+ shallowRenderer.render(<SomeComponent />, {});
95
var result = shallowRenderer.getRenderOutput();
96
expect(result.type).toBe('div');
97
expect(result.props.children).toEqual([
@@ -99,7 +99,7 @@ describe('ReactTestUtils', function() {
99
<span className="child2" />
100
]);
101
102
- shallowRenderer.render(<SomeComponent aNew="prop" />);
102
+ shallowRenderer.render(<SomeComponent aNew="prop" />, {});
103
var updatedResult = shallowRenderer.getRenderOutput();
104
expect(updatedResult.type).toBe('a');
105