@samitouri / QOS-React-2 / commits / b91396be8e

Contexts

Summary: adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy. Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals. The context starts out `null`: var RootComponent = React.createClass({ render: function() { // this.context === null } }); You should **never** mutate the context directly, just like props and state. You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`: var RootComponent = React.createClass({ render: function() { // this.context === null var children = React.withContext({foo: 'a', bar: 'b'}, () => ( // In ChildComponent#render, this.context === {foo: 'a', bar: 'b'} <ChildComponent /> )); // this.context === null } }); Contexts are merged, so a component can override its owner's context **for its children**: var ChildComponent = React.createClass({ render: function() { // this.context === {foo: 'a', bar: 'b'} (for the caller above) var children = React.withContext({foo: 'c'},() => ( // In GrandchildComponent#render, // this.context === {foo: 'c', bar: 'b'} <GrandchildComponent /> )); // this.context === {foo: 'a', bar: 'b'} } });

Marshall Roch committed Nov 18, 2013 at 10:41 UTC b91396be8eac3dc57ab1dbd2d3bb27c791d88169
11 files changed +455 -30
src/addons/transitions/ReactTransitionableChild.js
+1 -1
@@ -138,7 +138,7 @@ var ReactTransitionableChild = React.createClass({
138 }
139 },
140
141 - componentDidUpdate: function(prevProps, prevState, node) {
141 + componentDidUpdate: function(prevProps, prevState, prevContext, node) {
142 if (prevProps.children && !this.props.children) {
143 this.transition('leave', true, this.props.onDoneLeaving);
144 }
src/core/React.js
+2
@@ -20,6 +20,7 @@
20
21 var ReactComponent = require('ReactComponent');
22 var ReactCompositeComponent = require('ReactCompositeComponent');
23 +var ReactContext = require('ReactContext');
24 var ReactCurrentOwner = require('ReactCurrentOwner');
25 var ReactDOM = require('ReactDOM');
26 var ReactDOMComponent = require('ReactDOMComponent');
@@ -53,6 +54,7 @@ var React = {
54 unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
55 isValidClass: ReactCompositeComponent.isValidClass,
56 isValidComponent: ReactComponent.isValidComponent,
57 + withContext: ReactContext.withContext,
58 __internals: {
59 Component: ReactComponent,
60 CurrentOwner: ReactCurrentOwner,
src/core/ReactCompositeComponent.js
+157 -17
@@ -19,11 +19,13 @@
19 "use strict";
20
21 var ReactComponent = require('ReactComponent');
22 +var ReactContext = require('ReactContext');
23 var ReactCurrentOwner = require('ReactCurrentOwner');
24 var ReactErrorUtils = require('ReactErrorUtils');
25 var ReactOwner = require('ReactOwner');
26 var ReactPerf = require('ReactPerf');
27 var ReactPropTransferer = require('ReactPropTransferer');
28 +var ReactPropTypeLocations = require('ReactPropTypeLocations');
29 var ReactUpdates = require('ReactUpdates');
30
31 var invariant = require('invariant');
@@ -98,6 +100,21 @@ var ReactCompositeComponentInterface = {
100 */
101 propTypes: SpecPolicy.DEFINE_MANY_MERGED,
102
103 + /**
104 + * Definition of context types for this component.
105 + *
106 + * @type {object}
107 + * @optional
108 + */
109 + contextTypes: SpecPolicy.DEFINE_MANY_MERGED,
110 +
111 + /**
112 + * Definition of context types this component sets for its children.
113 + *
114 + * @type {object}
115 + * @optional
116 + */
117 + childContextTypes: SpecPolicy.DEFINE_MANY_MERGED,
118
119
120 // ==== Definition methods ====
@@ -130,6 +147,12 @@ var ReactCompositeComponentInterface = {
147 */
148 getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
149
150 + /**
151 + * @return {object}
152 + * @optional
153 + */
154 + getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
155 +
156 /**
157 * Uses props from `this.props` and state from `this.state` to render the
158 * structure of the component.
@@ -214,7 +237,8 @@ var ReactCompositeComponentInterface = {
237
238 /**
239 * Invoked when the component is about to update due to a transition from
217 - * `this.props` and `this.state` to `nextProps` and `nextState`.
240 + * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
241 + * and `nextContext`.
242 *
243 * Use this as an opportunity to perform preparation before an update occurs.
244 *
@@ -222,6 +246,7 @@ var ReactCompositeComponentInterface = {
246 *
247 * @param {object} nextProps
248 * @param {?object} nextState
249 + * @param {?object} nextContext
250 * @param {ReactReconcileTransaction} transaction
251 * @optional
252 */
@@ -235,6 +260,7 @@ var ReactCompositeComponentInterface = {
260 *
261 * @param {object} prevProps
262 * @param {?object} prevState
263 + * @param {?object} prevContext
264 * @param {DOMElement} rootNode DOM element representing the component.
265 * @optional
266 */
@@ -288,6 +314,12 @@ var RESERVED_SPEC_KEYS = {
314 }
315 }
316 },
317 + childContextTypes: function(Constructor, childContextTypes) {
318 + Constructor.childContextTypes = childContextTypes;
319 + },
320 + contextTypes: function(Constructor, contextTypes) {
321 + Constructor.contextTypes = contextTypes;
322 + },
323 propTypes: function(Constructor, propTypes) {
324 Constructor.propTypes = propTypes;
325 }
@@ -512,8 +544,14 @@ var ReactCompositeComponentMixin = {
544 construct: function(initialProps, children) {
545 // Children can be either an array or more than one argument
546 ReactComponent.Mixin.construct.apply(this, arguments);
547 +
548 this.state = null;
549 this._pendingState = null;
550 +
551 + this.context = this._processContext(ReactContext.current);
552 + this._currentContext = ReactContext.current;
553 + this._pendingContext = null;
554 +
555 this._compositeLifeCycleState = null;
556 },
557
@@ -659,6 +697,64 @@ var ReactCompositeComponentMixin = {
697 ReactUpdates.enqueueUpdate(this, callback);
698 },
699
700 + /**
701 + * Filters the context object to only contain keys specified in
702 + * `contextTypes`, and asserts that they are valid.
703 + *
704 + * @param {object} context
705 + * @return {?object}
706 + * @private
707 + */
708 + _processContext: function(context) {
709 + var maskedContext = null;
710 + var contextTypes = this.constructor.contextTypes;
711 + if (contextTypes) {
712 + maskedContext = {};
713 + for (var contextName in contextTypes) {
714 + maskedContext[contextName] = context[contextName];
715 + }
716 + this._checkPropTypes(
717 + contextTypes,
718 + maskedContext,
719 + ReactPropTypeLocations.context
720 + );
721 + }
722 + return maskedContext;
723 + },
724 +
725 + /**
726 + * @param {object} currentContext
727 + * @return {object}
728 + * @private
729 + */
730 + _processChildContext: function(currentContext) {
731 + var childContext = this.getChildContext && this.getChildContext();
732 + var displayName = this.constructor.displayName || 'ReactCompositeComponent';
733 + if (childContext) {
734 + invariant(
735 + typeof this.constructor.childContextTypes === 'object',
736 + '%s.getChildContext(): childContextTypes must be defined in order to ' +
737 + 'use getChildContext().',
738 + displayName
739 + );
740 + this._checkPropTypes(
741 + this.constructor.childContextTypes,
742 + childContext,
743 + ReactPropTypeLocations.childContext
744 + );
745 + for (var name in childContext) {
746 + invariant(
747 + name in this.constructor.childContextTypes,
748 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
749 + displayName,
750 + name
751 + );
752 + }
753 + return merge(currentContext, childContext);
754 + }
755 + return currentContext;
756 + },
757 +
758 /**
759 * Processes props by setting default values for unspecified props and
760 * asserting that the props are valid.
@@ -667,21 +763,32 @@ var ReactCompositeComponentMixin = {
763 * @private
764 */
765 _processProps: function(props) {
670 - var propName;
766 var defaultProps = this._defaultProps;
672 - for (propName in defaultProps) {
767 + for (var propName in defaultProps) {
768 if (typeof props[propName] === 'undefined') {
769 props[propName] = defaultProps[propName];
770 }
771 }
772 var propTypes = this.constructor.propTypes;
773 if (propTypes) {
679 - var componentName = this.constructor.displayName;
680 - for (propName in propTypes) {
681 - var checkProp = propTypes[propName];
682 - if (checkProp) {
683 - checkProp(props, propName, componentName);
684 - }
774 + this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop);
775 + }
776 + },
777 +
778 + /**
779 + * Assert that the props are valid
780 + *
781 + * @param {object} propTypes Map of prop name to a ReactPropType
782 + * @param {object} props
783 + * @param {string} location e.g. "prop", "context", "child context"
784 + * @private
785 + */
786 + _checkPropTypes: function(propTypes, props, location) {
787 + var componentName = this.constructor.displayName;
788 + for (var propName in propTypes) {
789 + var checkProp = propTypes[propName];
790 + if (checkProp) {
791 + checkProp(props, propName, componentName, location);
792 }
793 }
794 },
@@ -707,6 +814,7 @@ var ReactCompositeComponentMixin = {
814 _performUpdateIfNecessary: function(transaction) {
815 if (this._pendingProps == null &&
816 this._pendingState == null &&
817 + this._pendingContext == null &&
818 !this._pendingForceUpdate) {
819 return;
820 }
@@ -728,17 +836,26 @@ var ReactCompositeComponentMixin = {
836 var nextState = this._pendingState || this.state;
837 this._pendingState = null;
838
839 + var nextContext = this._pendingContext || this._currentContext;
840 + this._pendingContext = null;
841 +
842 if (this._pendingForceUpdate ||
843 !this.shouldComponentUpdate ||
733 - this.shouldComponentUpdate(nextProps, nextState)) {
844 + this.shouldComponentUpdate(nextProps, nextState, nextContext)) {
845 this._pendingForceUpdate = false;
735 - // Will set `this.props` and `this.state`.
736 - this._performComponentUpdate(nextProps, nextState, transaction);
846 + // Will set `this.props`, `this.state` and `this.context`.
847 + this._performComponentUpdate(
848 + nextProps,
849 + nextState,
850 + nextContext,
851 + transaction
852 + );
853 } else {
854 // If it's determined that a component should not update, we still want
855 // to set props and state.
856 this.props = nextProps;
857 this.state = nextState;
858 + this.context = nextContext;
859 }
860
861 this._compositeLifeCycleState = null;
@@ -750,30 +867,49 @@ var ReactCompositeComponentMixin = {
867 *
868 * @param {object} nextProps Next object to set as properties.
869 * @param {?object} nextState Next object to set as state.
870 + * @param {?object} nextContext Next object to set as context.
871 * @param {ReactReconcileTransaction} transaction
872 * @private
873 */
756 - _performComponentUpdate: function(nextProps, nextState, transaction) {
874 + _performComponentUpdate: function(
875 + nextProps,
876 + nextState,
877 + nextContext,
878 + transaction
879 + ) {
880 var prevProps = this.props;
881 var prevState = this.state;
882 + var prevContext = this.context;
883
884 if (this.componentWillUpdate) {
761 - this.componentWillUpdate(nextProps, nextState);
885 + this.componentWillUpdate(nextProps, nextState, nextContext);
886 }
887
888 this.props = nextProps;
889 this.state = nextState;
890
767 - this.updateComponent(transaction, prevProps, prevState);
891 + this._currentContext = nextContext;
892 + this.context = this._processContext(nextContext);
893 +
894 + this.updateComponent(transaction, prevProps, prevState, prevContext);
895
896 if (this.componentDidUpdate) {
897 transaction.getReactMountReady().enqueue(
898 this,
772 - this.componentDidUpdate.bind(this, prevProps, prevState)
899 + this.componentDidUpdate.bind(this, prevProps, prevState, prevContext)
900 );
901 }
902 },
903
904 + receiveComponent: function(nextComponent, transaction) {
905 + this._pendingContext = nextComponent._currentContext;
906 + ReactComponent.Mixin.receiveComponent.call(
907 + this,
908 + nextComponent,
909 + transaction
910 + );
911 + },
912 +
913 /**
914 * Updates the component's currently mounted DOM representation.
915 *
@@ -783,13 +919,14 @@ var ReactCompositeComponentMixin = {
919 * @param {ReactReconcileTransaction} transaction
920 * @param {object} prevProps
921 * @param {?object} prevState
922 + * @param {?object} prevContext
923 * @internal
924 * @overridable
925 */
926 updateComponent: ReactPerf.measure(
927 'ReactCompositeComponent',
928 'updateComponent',
792 - function(transaction, prevProps, prevState) {
929 + function(transaction, prevProps, prevState, prevContext) {
930 ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
931 var prevComponent = this._renderedComponent;
932 var nextComponent = this._renderValidatedComponent();
@@ -851,6 +988,8 @@ var ReactCompositeComponentMixin = {
988 */
989 _renderValidatedComponent: function() {
990 var renderedComponent;
991 + var previousContext = ReactContext.current;
992 + ReactContext.current = this._processChildContext(this._currentContext);
993 ReactCurrentOwner.current = this;
994 try {
995 renderedComponent = this.render();
@@ -858,6 +997,7 @@ var ReactCompositeComponentMixin = {
997 // IE8 requires `catch` in order to use `finally`.
998 throw error;
999 } finally {
1000 + ReactContext.current = previousContext;
1001 ReactCurrentOwner.current = null;
1002 }
1003 invariant(
src/core/ReactContext.js new
+70
@@ -0,0 +1,70 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactContext
17 + */
18 +
19 +"use strict";
20 +
21 +var merge = require('merge');
22 +
23 +/**
24 + * Keeps track of the current context.
25 + *
26 + * The context is automatically passed down the component ownership hierarchy
27 + * and is accessible via `this.context` on ReactCompositeComponents.
28 + */
29 +var ReactContext = {
30 +
31 + /**
32 + * @internal
33 + * @type {object}
34 + */
35 + current: {},
36 +
37 + /**
38 + * Temporarily extends the current context while executing scopedCallback.
39 + *
40 + * A typical use case might look like
41 + *
42 + * render: function() {
43 + * var children = ReactContext.withContext({foo: 'foo'} () => (
44 + *
45 + * ));
46 + * return <div>{children}</div>;
47 + * }
48 + *
49 + * @param {object} newContext New context to merge into the existing context
50 + * @param {function} scopedCallback Callback to run with the new context
51 + * @return {ReactComponent|array<ReactComponent>}
52 + */
53 + withContext: function(newContext, scopedCallback) {
54 + var result;
55 + var previousContext = ReactContext.current;
56 + ReactContext.current = merge(previousContext, newContext);
57 + try {
58 + result = scopedCallback();
59 + } catch (error) {
60 + // IE8 requires `catch` in order to use `finally`.
61 + throw error;
62 + } finally {
63 + ReactContext.current = previousContext;
64 + }
65 + return result;
66 + }
67 +
68 +};
69 +
70 +module.exports = ReactContext;
src/core/ReactPropTypeLocations.js new
+29
@@ -0,0 +1,29 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactPropTypeLocations
17 + */
18 +
19 +"use strict";
20 +
21 +var keyMirror = require('keyMirror');
22 +
23 +var ReactPropTypeLocations = keyMirror({
24 + props: null,
25 + context: null,
26 + childContext: null
27 +});
28 +
29 +module.exports = ReactPropTypeLocations;
src/core/ReactPropTypes.js
+27 -9
@@ -18,6 +18,8 @@
18
19 "use strict";
20
21 +var ReactPropTypeLocations = require('ReactPropTypeLocations');
22 +
23 var createObjectFrom = require('createObjectFrom');
24 var invariant = require('invariant');
25
@@ -87,14 +89,18 @@ var Props = {
89 var ANONYMOUS = '<<anonymous>>';
90
91 function createPrimitiveTypeChecker(expectedType) {
90 - function validatePrimitiveType(propValue, propName, componentName) {
92 + function validatePrimitiveType(propValue, propName, componentName, location) {
93 var propType = typeof propValue;
94 if (propType === 'object' && Array.isArray(propValue)) {
95 propType = 'array';
96 }
97 invariant(
98 propType === expectedType,
97 - 'Invalid prop `%s` of type `%s` supplied to `%s`, expected `%s`.',
99 + 'Invalid %s `%s` of type `%s` supplied to `%s`, expected `%s`.',
100 + (location === ReactPropTypeLocations.prop ? 'prop' :
101 + (location === ReactPropTypeLocations.context ? 'context' :
102 + (location === ReactPropTypeLocations.childContext ? 'child context' :
103 + '<unknown location>'))),
104 propName,
105 propType,
106 componentName,
@@ -106,10 +112,14 @@ function createPrimitiveTypeChecker(expectedType) {
112
113 function createEnumTypeChecker(expectedValues) {
114 var expectedEnum = createObjectFrom(expectedValues);
109 - function validateEnumType(propValue, propName, componentName) {
115 + function validateEnumType(propValue, propName, componentName, location) {
116 invariant(
117 expectedEnum[propValue],
112 - 'Invalid prop `%s` supplied to `%s`, expected one of %s.',
118 + 'Invalid %s `%s` supplied to `%s`, expected one of %s.',
119 + (location === ReactPropTypeLocations.prop ? 'prop' :
120 + (location === ReactPropTypeLocations.context ? 'context' :
121 + (location === ReactPropTypeLocations.childContext ? 'child context' :
122 + '<unknown location>'))),
123 propName,
124 componentName,
125 JSON.stringify(Object.keys(expectedEnum))
@@ -119,10 +129,14 @@ function createEnumTypeChecker(expectedValues) {
129 }
130
131 function createInstanceTypeChecker(expectedClass) {
122 - function validateInstanceType(propValue, propName, componentName) {
132 + function validateInstanceType(propValue, propName, componentName, location) {
133 invariant(
134 propValue instanceof expectedClass,
125 - 'Invalid prop `%s` supplied to `%s`, expected instance of `%s`.',
135 + 'Invalid %s `%s` supplied to `%s`, expected instance of `%s`.',
136 + (location === ReactPropTypeLocations.prop ? 'prop' :
137 + (location === ReactPropTypeLocations.context ? 'context' :
138 + (location === ReactPropTypeLocations.childContext ? 'child context' :
139 + '<unknown location>'))),
140 propName,
141 componentName,
142 expectedClass.name || ANONYMOUS
@@ -133,15 +147,19 @@ function createInstanceTypeChecker(expectedClass) {
147
148 function createChainableTypeChecker(validate) {
149 function createTypeChecker(isRequired) {
136 - function checkType(props, propName, componentName) {
150 + function checkType(props, propName, componentName, location) {
151 var propValue = props[propName];
152 if (propValue != null) {
153 // Only validate if there is a value to check.
140 - validate(propValue, propName, componentName || ANONYMOUS);
154 + validate(propValue, propName, componentName || ANONYMOUS, location);
155 } else {
156 invariant(
157 !isRequired,
144 - 'Required prop `%s` was not specified in `%s`.',
158 + 'Required %s `%s` was not specified in `%s`.',
159 + (location === ReactPropTypeLocations.prop ?
160 + 'prop' : (location === ReactPropTypeLocations.context ?
161 + 'context' : (location === ReactPropTypeLocations.childContext ?
162 + 'child context' : '<unknown location>'))),
163 propName,
164 componentName || ANONYMOUS
165 );
src/core/__tests__/ReactCompositeComponent-test.js
+143
@@ -476,4 +476,147 @@ describe('ReactCompositeComponent', function() {
476 console.warn = warn;
477 }
478 });
479 +
480 + it('should pass context', function() {
481 + var childInstance = null;
482 + var grandchildInstance = null;
483 +
484 + var Parent = React.createClass({
485 + childContextTypes: {
486 + foo: ReactPropTypes.string,
487 + depth: ReactPropTypes.number
488 + },
489 +
490 + getChildContext: function() {
491 + return {
492 + foo: 'bar',
493 + depth: 0
494 + };
495 + },
496 +
497 + render: function() {
498 + childInstance = <Child />;
499 + return childInstance;
500 + }
501 + });
502 +
503 + var Child = React.createClass({
504 + contextTypes: {
505 + foo: ReactPropTypes.string,
506 + depth: ReactPropTypes.number
507 + },
508 +
509 + childContextTypes: {
510 + depth: ReactPropTypes.number
511 + },
512 +
513 + getChildContext: function() {
514 + return {
515 + depth: this.context.depth + 1
516 + };
517 + },
518 +
519 + render: function() {
520 + grandchildInstance = <Grandchild />;
521 + return grandchildInstance;
522 + }
523 + });
524 +
525 + var Grandchild = React.createClass({
526 + contextTypes: {
527 + foo: ReactPropTypes.string,
528 + depth: ReactPropTypes.number
529 + },
530 +
531 + render: function() {
532 + return <div />;
533 + }
534 + });
535 +
536 + var instance = <Parent />;
537 + ReactTestUtils.renderIntoDocument(instance);
538 + reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', depth: 0});
539 + reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
540 + });
541 +
542 + it('should check context types', function() {
543 + var Component = React.createClass({
544 + contextTypes: {
545 + foo: ReactPropTypes.string.isRequired
546 + },
547 +
548 + render: function() {
549 + return <div />;
550 + }
551 + });
552 +
553 + expect(function() {
554 + ReactTestUtils.renderIntoDocument(<Component />);
555 + }).toThrow(
556 + 'Invariant Violation: Required context `foo` was not specified in ' +
557 + '`Component`.'
558 + );
559 +
560 + expect(function() {
561 + React.withContext({foo: 'bar'}, function() {
562 + ReactTestUtils.renderIntoDocument(<Component />);
563 + });
564 + }).not.toThrow();
565 +
566 + expect(function() {
567 + React.withContext({foo: 123}, function() {
568 + ReactTestUtils.renderIntoDocument(<Component />);
569 + });
570 + }).toThrow(
571 + 'Invariant Violation: Invalid context `foo` of type `number` supplied ' +
572 + 'to `Component`, expected `string`.'
573 + );
574 + });
575 +
576 + it('should check child context types', function() {
577 + var Component = React.createClass({
578 + childContextTypes: {
579 + foo: ReactPropTypes.string.isRequired,
580 + bar: ReactPropTypes.number
581 + },
582 +
583 + getChildContext: function() {
584 + return this.props.testContext;
585 + },
586 +
587 + render: function() {
588 + return <div />;
589 + }
590 + });
591 +
592 + expect(function() {
593 + ReactTestUtils.renderIntoDocument(
594 + <Component testContext={{bar: 123}} />
595 + );
596 + }).toThrow(
597 + 'Invariant Violation: Required child context `foo` was not specified ' +
598 + 'in `Component`.'
599 + );
600 +
601 + expect(function() {
602 + ReactTestUtils.renderIntoDocument(
603 + <Component testContext={{foo: 123}} />
604 + );
605 + }).toThrow(
606 + 'Invariant Violation: Invalid child context `foo` of type `number` ' +
607 + 'supplied to `Component`, expected `string`.'
608 + );
609 +
610 + expect(function() {
611 + ReactTestUtils.renderIntoDocument(
612 + <Component testContext={{foo: 'foo', bar: 123}} />
613 + );
614 + }).not.toThrow();
615 +
616 + expect(function() {
617 + ReactTestUtils.renderIntoDocument(
618 + <Component testContext={{foo: 'foo'}} />
619 + );
620 + }).not.toThrow();
621 + });
622 });
src/core/__tests__/ReactPropTypes-test.js
+8 -1
@@ -20,13 +20,20 @@
20 "use strict";
21
22 var Props = require('ReactPropTypes');
23 +var ReactPropTypeLocations = require('ReactPropTypeLocations');
24
25 function typeCheck(declaration, value) {
26 var props = {};
27 if (arguments.length > 1) {
28 props.testProp = value;
29 }
29 - return declaration.bind(null, props, 'testProp', 'testComponent');
30 + return declaration.bind(
31 + null,
32 + props,
33 + 'testProp',
34 + 'testComponent',
35 + ReactPropTypeLocations.prop
36 + );
37 }
38
39 describe('Primitive Types', function() {
src/dom/components/ReactDOMInput.js
+1 -1
@@ -92,7 +92,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
92 delete instancesByReactID[id];
93 },
94
95 - componentDidUpdate: function(prevProps, prevState, rootNode) {
95 + componentDidUpdate: function(prevProps, prevState, prevContext, rootNode) {
96 if (this.props.checked != null) {
97 DOMPropertyOperations.setValueForProperty(
98 rootNode,
src/dom/components/ReactDOMTextarea.js
+1 -1
@@ -110,7 +110,7 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
110 return textarea(props, this.state.initialValue);
111 },
112
113 - componentDidUpdate: function(prevProps, prevState, rootNode) {
113 + componentDidUpdate: function(prevProps, prevState, prevContext, rootNode) {
114 var value = this.getValue();
115 if (value != null) {
116 // Cast `value` to a string to ensure the value is set correctly. While
src/test/reactComponentExpect.js
+16
@@ -196,6 +196,22 @@ mergeInto(reactComponentExpect.prototype, {
196 .toEqual(propNameToExpectedValue[propName]);
197 }
198 return this;
199 + },
200 +
201 + /**
202 + * Check a set of props are equal to a set of expected values - only works
203 + * with scalars.
204 + */
205 + scalarContextEqual: function(contextNameToExpectedValue) {
206 + expect(this.instance()).toBeTruthy();
207 + for (var contextName in contextNameToExpectedValue) {
208 + if (!contextNameToExpectedValue.hasOwnProperty(contextName)) {
209 + continue;
210 + }
211 + expect(this.instance().context[contextName])
212 + .toEqual(contextNameToExpectedValue[contextName]);
213 + }
214 + return this;
215 }
216 });
217