@samitouri / QOS-React-2 / commits / 081feeb2dd

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