mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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'}
}
});
This commit is contained in:
committed by
Paul O’Shannessy
parent
7df127db31
commit
b91396be8e
@@ -138,7 +138,7 @@ var ReactTransitionableChild = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, node) {
|
||||
componentDidUpdate: function(prevProps, prevState, prevContext, node) {
|
||||
if (prevProps.children && !this.props.children) {
|
||||
this.transition('leave', true, this.props.onDoneLeaving);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactContext = require('ReactContext');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
@@ -53,6 +54,7 @@ var React = {
|
||||
unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
|
||||
isValidClass: ReactCompositeComponent.isValidClass,
|
||||
isValidComponent: ReactComponent.isValidComponent,
|
||||
withContext: ReactContext.withContext,
|
||||
__internals: {
|
||||
Component: ReactComponent,
|
||||
CurrentOwner: ReactCurrentOwner,
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
"use strict";
|
||||
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactContext = require('ReactContext');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactErrorUtils = require('ReactErrorUtils');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactPropTransferer = require('ReactPropTransferer');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var invariant = require('invariant');
|
||||
@@ -98,6 +100,21 @@ var ReactCompositeComponentInterface = {
|
||||
*/
|
||||
propTypes: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Definition of context types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
contextTypes: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Definition of context types this component sets for its children.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
childContextTypes: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
|
||||
// ==== Definition methods ====
|
||||
@@ -130,6 +147,12 @@ var ReactCompositeComponentInterface = {
|
||||
*/
|
||||
getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Uses props from `this.props` and state from `this.state` to render the
|
||||
* structure of the component.
|
||||
@@ -214,7 +237,8 @@ var ReactCompositeComponentInterface = {
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to update due to a transition from
|
||||
* `this.props` and `this.state` to `nextProps` and `nextState`.
|
||||
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
|
||||
* and `nextContext`.
|
||||
*
|
||||
* Use this as an opportunity to perform preparation before an update occurs.
|
||||
*
|
||||
@@ -222,6 +246,7 @@ var ReactCompositeComponentInterface = {
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @optional
|
||||
*/
|
||||
@@ -235,6 +260,7 @@ var ReactCompositeComponentInterface = {
|
||||
*
|
||||
* @param {object} prevProps
|
||||
* @param {?object} prevState
|
||||
* @param {?object} prevContext
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
@@ -288,6 +314,12 @@ var RESERVED_SPEC_KEYS = {
|
||||
}
|
||||
}
|
||||
},
|
||||
childContextTypes: function(Constructor, childContextTypes) {
|
||||
Constructor.childContextTypes = childContextTypes;
|
||||
},
|
||||
contextTypes: function(Constructor, contextTypes) {
|
||||
Constructor.contextTypes = contextTypes;
|
||||
},
|
||||
propTypes: function(Constructor, propTypes) {
|
||||
Constructor.propTypes = propTypes;
|
||||
}
|
||||
@@ -512,8 +544,14 @@ var ReactCompositeComponentMixin = {
|
||||
construct: function(initialProps, children) {
|
||||
// Children can be either an array or more than one argument
|
||||
ReactComponent.Mixin.construct.apply(this, arguments);
|
||||
|
||||
this.state = null;
|
||||
this._pendingState = null;
|
||||
|
||||
this.context = this._processContext(ReactContext.current);
|
||||
this._currentContext = ReactContext.current;
|
||||
this._pendingContext = null;
|
||||
|
||||
this._compositeLifeCycleState = null;
|
||||
},
|
||||
|
||||
@@ -659,6 +697,64 @@ var ReactCompositeComponentMixin = {
|
||||
ReactUpdates.enqueueUpdate(this, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the context object to only contain keys specified in
|
||||
* `contextTypes`, and asserts that they are valid.
|
||||
*
|
||||
* @param {object} context
|
||||
* @return {?object}
|
||||
* @private
|
||||
*/
|
||||
_processContext: function(context) {
|
||||
var maskedContext = null;
|
||||
var contextTypes = this.constructor.contextTypes;
|
||||
if (contextTypes) {
|
||||
maskedContext = {};
|
||||
for (var contextName in contextTypes) {
|
||||
maskedContext[contextName] = context[contextName];
|
||||
}
|
||||
this._checkPropTypes(
|
||||
contextTypes,
|
||||
maskedContext,
|
||||
ReactPropTypeLocations.context
|
||||
);
|
||||
}
|
||||
return maskedContext;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} currentContext
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_processChildContext: function(currentContext) {
|
||||
var childContext = this.getChildContext && this.getChildContext();
|
||||
var displayName = this.constructor.displayName || 'ReactCompositeComponent';
|
||||
if (childContext) {
|
||||
invariant(
|
||||
typeof this.constructor.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
displayName
|
||||
);
|
||||
this._checkPropTypes(
|
||||
this.constructor.childContextTypes,
|
||||
childContext,
|
||||
ReactPropTypeLocations.childContext
|
||||
);
|
||||
for (var name in childContext) {
|
||||
invariant(
|
||||
name in this.constructor.childContextTypes,
|
||||
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
||||
displayName,
|
||||
name
|
||||
);
|
||||
}
|
||||
return merge(currentContext, childContext);
|
||||
}
|
||||
return currentContext;
|
||||
},
|
||||
|
||||
/**
|
||||
* Processes props by setting default values for unspecified props and
|
||||
* asserting that the props are valid.
|
||||
@@ -667,21 +763,32 @@ var ReactCompositeComponentMixin = {
|
||||
* @private
|
||||
*/
|
||||
_processProps: function(props) {
|
||||
var propName;
|
||||
var defaultProps = this._defaultProps;
|
||||
for (propName in defaultProps) {
|
||||
for (var propName in defaultProps) {
|
||||
if (typeof props[propName] === 'undefined') {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
var propTypes = this.constructor.propTypes;
|
||||
if (propTypes) {
|
||||
var componentName = this.constructor.displayName;
|
||||
for (propName in propTypes) {
|
||||
var checkProp = propTypes[propName];
|
||||
if (checkProp) {
|
||||
checkProp(props, propName, componentName);
|
||||
}
|
||||
this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Assert that the props are valid
|
||||
*
|
||||
* @param {object} propTypes Map of prop name to a ReactPropType
|
||||
* @param {object} props
|
||||
* @param {string} location e.g. "prop", "context", "child context"
|
||||
* @private
|
||||
*/
|
||||
_checkPropTypes: function(propTypes, props, location) {
|
||||
var componentName = this.constructor.displayName;
|
||||
for (var propName in propTypes) {
|
||||
var checkProp = propTypes[propName];
|
||||
if (checkProp) {
|
||||
checkProp(props, propName, componentName, location);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -707,6 +814,7 @@ var ReactCompositeComponentMixin = {
|
||||
_performUpdateIfNecessary: function(transaction) {
|
||||
if (this._pendingProps == null &&
|
||||
this._pendingState == null &&
|
||||
this._pendingContext == null &&
|
||||
!this._pendingForceUpdate) {
|
||||
return;
|
||||
}
|
||||
@@ -728,17 +836,26 @@ var ReactCompositeComponentMixin = {
|
||||
var nextState = this._pendingState || this.state;
|
||||
this._pendingState = null;
|
||||
|
||||
var nextContext = this._pendingContext || this._currentContext;
|
||||
this._pendingContext = null;
|
||||
|
||||
if (this._pendingForceUpdate ||
|
||||
!this.shouldComponentUpdate ||
|
||||
this.shouldComponentUpdate(nextProps, nextState)) {
|
||||
this.shouldComponentUpdate(nextProps, nextState, nextContext)) {
|
||||
this._pendingForceUpdate = false;
|
||||
// Will set `this.props` and `this.state`.
|
||||
this._performComponentUpdate(nextProps, nextState, transaction);
|
||||
// Will set `this.props`, `this.state` and `this.context`.
|
||||
this._performComponentUpdate(
|
||||
nextProps,
|
||||
nextState,
|
||||
nextContext,
|
||||
transaction
|
||||
);
|
||||
} else {
|
||||
// If it's determined that a component should not update, we still want
|
||||
// to set props and state.
|
||||
this.props = nextProps;
|
||||
this.state = nextState;
|
||||
this.context = nextContext;
|
||||
}
|
||||
|
||||
this._compositeLifeCycleState = null;
|
||||
@@ -750,30 +867,49 @@ var ReactCompositeComponentMixin = {
|
||||
*
|
||||
* @param {object} nextProps Next object to set as properties.
|
||||
* @param {?object} nextState Next object to set as state.
|
||||
* @param {?object} nextContext Next object to set as context.
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @private
|
||||
*/
|
||||
_performComponentUpdate: function(nextProps, nextState, transaction) {
|
||||
_performComponentUpdate: function(
|
||||
nextProps,
|
||||
nextState,
|
||||
nextContext,
|
||||
transaction
|
||||
) {
|
||||
var prevProps = this.props;
|
||||
var prevState = this.state;
|
||||
var prevContext = this.context;
|
||||
|
||||
if (this.componentWillUpdate) {
|
||||
this.componentWillUpdate(nextProps, nextState);
|
||||
this.componentWillUpdate(nextProps, nextState, nextContext);
|
||||
}
|
||||
|
||||
this.props = nextProps;
|
||||
this.state = nextState;
|
||||
|
||||
this.updateComponent(transaction, prevProps, prevState);
|
||||
this._currentContext = nextContext;
|
||||
this.context = this._processContext(nextContext);
|
||||
|
||||
this.updateComponent(transaction, prevProps, prevState, prevContext);
|
||||
|
||||
if (this.componentDidUpdate) {
|
||||
transaction.getReactMountReady().enqueue(
|
||||
this,
|
||||
this.componentDidUpdate.bind(this, prevProps, prevState)
|
||||
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
receiveComponent: function(nextComponent, transaction) {
|
||||
this._pendingContext = nextComponent._currentContext;
|
||||
ReactComponent.Mixin.receiveComponent.call(
|
||||
this,
|
||||
nextComponent,
|
||||
transaction
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted DOM representation.
|
||||
*
|
||||
@@ -783,13 +919,14 @@ var ReactCompositeComponentMixin = {
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @param {object} prevProps
|
||||
* @param {?object} prevState
|
||||
* @param {?object} prevContext
|
||||
* @internal
|
||||
* @overridable
|
||||
*/
|
||||
updateComponent: ReactPerf.measure(
|
||||
'ReactCompositeComponent',
|
||||
'updateComponent',
|
||||
function(transaction, prevProps, prevState) {
|
||||
function(transaction, prevProps, prevState, prevContext) {
|
||||
ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
|
||||
var prevComponent = this._renderedComponent;
|
||||
var nextComponent = this._renderValidatedComponent();
|
||||
@@ -851,6 +988,8 @@ var ReactCompositeComponentMixin = {
|
||||
*/
|
||||
_renderValidatedComponent: function() {
|
||||
var renderedComponent;
|
||||
var previousContext = ReactContext.current;
|
||||
ReactContext.current = this._processChildContext(this._currentContext);
|
||||
ReactCurrentOwner.current = this;
|
||||
try {
|
||||
renderedComponent = this.render();
|
||||
@@ -858,6 +997,7 @@ var ReactCompositeComponentMixin = {
|
||||
// IE8 requires `catch` in order to use `finally`.
|
||||
throw error;
|
||||
} finally {
|
||||
ReactContext.current = previousContext;
|
||||
ReactCurrentOwner.current = null;
|
||||
}
|
||||
invariant(
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactContext
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var merge = require('merge');
|
||||
|
||||
/**
|
||||
* Keeps track of the current context.
|
||||
*
|
||||
* The context is automatically passed down the component ownership hierarchy
|
||||
* and is accessible via `this.context` on ReactCompositeComponents.
|
||||
*/
|
||||
var ReactContext = {
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @type {object}
|
||||
*/
|
||||
current: {},
|
||||
|
||||
/**
|
||||
* Temporarily extends the current context while executing scopedCallback.
|
||||
*
|
||||
* A typical use case might look like
|
||||
*
|
||||
* render: function() {
|
||||
* var children = ReactContext.withContext({foo: 'foo'} () => (
|
||||
*
|
||||
* ));
|
||||
* return <div>{children}</div>;
|
||||
* }
|
||||
*
|
||||
* @param {object} newContext New context to merge into the existing context
|
||||
* @param {function} scopedCallback Callback to run with the new context
|
||||
* @return {ReactComponent|array<ReactComponent>}
|
||||
*/
|
||||
withContext: function(newContext, scopedCallback) {
|
||||
var result;
|
||||
var previousContext = ReactContext.current;
|
||||
ReactContext.current = merge(previousContext, newContext);
|
||||
try {
|
||||
result = scopedCallback();
|
||||
} catch (error) {
|
||||
// IE8 requires `catch` in order to use `finally`.
|
||||
throw error;
|
||||
} finally {
|
||||
ReactContext.current = previousContext;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactContext;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactPropTypeLocations
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var keyMirror = require('keyMirror');
|
||||
|
||||
var ReactPropTypeLocations = keyMirror({
|
||||
props: null,
|
||||
context: null,
|
||||
childContext: null
|
||||
});
|
||||
|
||||
module.exports = ReactPropTypeLocations;
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
|
||||
var createObjectFrom = require('createObjectFrom');
|
||||
var invariant = require('invariant');
|
||||
|
||||
@@ -87,14 +89,18 @@ var Props = {
|
||||
var ANONYMOUS = '<<anonymous>>';
|
||||
|
||||
function createPrimitiveTypeChecker(expectedType) {
|
||||
function validatePrimitiveType(propValue, propName, componentName) {
|
||||
function validatePrimitiveType(propValue, propName, componentName, location) {
|
||||
var propType = typeof propValue;
|
||||
if (propType === 'object' && Array.isArray(propValue)) {
|
||||
propType = 'array';
|
||||
}
|
||||
invariant(
|
||||
propType === expectedType,
|
||||
'Invalid prop `%s` of type `%s` supplied to `%s`, expected `%s`.',
|
||||
'Invalid %s `%s` of type `%s` supplied to `%s`, expected `%s`.',
|
||||
(location === ReactPropTypeLocations.prop ? 'prop' :
|
||||
(location === ReactPropTypeLocations.context ? 'context' :
|
||||
(location === ReactPropTypeLocations.childContext ? 'child context' :
|
||||
'<unknown location>'))),
|
||||
propName,
|
||||
propType,
|
||||
componentName,
|
||||
@@ -106,10 +112,14 @@ function createPrimitiveTypeChecker(expectedType) {
|
||||
|
||||
function createEnumTypeChecker(expectedValues) {
|
||||
var expectedEnum = createObjectFrom(expectedValues);
|
||||
function validateEnumType(propValue, propName, componentName) {
|
||||
function validateEnumType(propValue, propName, componentName, location) {
|
||||
invariant(
|
||||
expectedEnum[propValue],
|
||||
'Invalid prop `%s` supplied to `%s`, expected one of %s.',
|
||||
'Invalid %s `%s` supplied to `%s`, expected one of %s.',
|
||||
(location === ReactPropTypeLocations.prop ? 'prop' :
|
||||
(location === ReactPropTypeLocations.context ? 'context' :
|
||||
(location === ReactPropTypeLocations.childContext ? 'child context' :
|
||||
'<unknown location>'))),
|
||||
propName,
|
||||
componentName,
|
||||
JSON.stringify(Object.keys(expectedEnum))
|
||||
@@ -119,10 +129,14 @@ function createEnumTypeChecker(expectedValues) {
|
||||
}
|
||||
|
||||
function createInstanceTypeChecker(expectedClass) {
|
||||
function validateInstanceType(propValue, propName, componentName) {
|
||||
function validateInstanceType(propValue, propName, componentName, location) {
|
||||
invariant(
|
||||
propValue instanceof expectedClass,
|
||||
'Invalid prop `%s` supplied to `%s`, expected instance of `%s`.',
|
||||
'Invalid %s `%s` supplied to `%s`, expected instance of `%s`.',
|
||||
(location === ReactPropTypeLocations.prop ? 'prop' :
|
||||
(location === ReactPropTypeLocations.context ? 'context' :
|
||||
(location === ReactPropTypeLocations.childContext ? 'child context' :
|
||||
'<unknown location>'))),
|
||||
propName,
|
||||
componentName,
|
||||
expectedClass.name || ANONYMOUS
|
||||
@@ -133,15 +147,19 @@ function createInstanceTypeChecker(expectedClass) {
|
||||
|
||||
function createChainableTypeChecker(validate) {
|
||||
function createTypeChecker(isRequired) {
|
||||
function checkType(props, propName, componentName) {
|
||||
function checkType(props, propName, componentName, location) {
|
||||
var propValue = props[propName];
|
||||
if (propValue != null) {
|
||||
// Only validate if there is a value to check.
|
||||
validate(propValue, propName, componentName || ANONYMOUS);
|
||||
validate(propValue, propName, componentName || ANONYMOUS, location);
|
||||
} else {
|
||||
invariant(
|
||||
!isRequired,
|
||||
'Required prop `%s` was not specified in `%s`.',
|
||||
'Required %s `%s` was not specified in `%s`.',
|
||||
(location === ReactPropTypeLocations.prop ?
|
||||
'prop' : (location === ReactPropTypeLocations.context ?
|
||||
'context' : (location === ReactPropTypeLocations.childContext ?
|
||||
'child context' : '<unknown location>'))),
|
||||
propName,
|
||||
componentName || ANONYMOUS
|
||||
);
|
||||
|
||||
@@ -476,4 +476,147 @@ describe('ReactCompositeComponent', function() {
|
||||
console.warn = warn;
|
||||
}
|
||||
});
|
||||
|
||||
it('should pass context', function() {
|
||||
var childInstance = null;
|
||||
var grandchildInstance = null;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string,
|
||||
depth: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: 'bar',
|
||||
depth: 0
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
childInstance = <Child />;
|
||||
return childInstance;
|
||||
}
|
||||
});
|
||||
|
||||
var Child = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string,
|
||||
depth: ReactPropTypes.number
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
depth: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
depth: this.context.depth + 1
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
grandchildInstance = <Grandchild />;
|
||||
return grandchildInstance;
|
||||
}
|
||||
});
|
||||
|
||||
var Grandchild = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string,
|
||||
depth: ReactPropTypes.number
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var instance = <Parent />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', depth: 0});
|
||||
reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
|
||||
});
|
||||
|
||||
it('should check context types', function() {
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Required context `foo` was not specified in ' +
|
||||
'`Component`.'
|
||||
);
|
||||
|
||||
expect(function() {
|
||||
React.withContext({foo: 'bar'}, function() {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
});
|
||||
}).not.toThrow();
|
||||
|
||||
expect(function() {
|
||||
React.withContext({foo: 123}, function() {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check child context types', function() {
|
||||
var Component = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string.isRequired,
|
||||
bar: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return this.props.testContext;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{bar: 123}} />
|
||||
);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Required child context `foo` was not specified ' +
|
||||
'in `Component`.'
|
||||
);
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 123}} />
|
||||
);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.'
|
||||
);
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo', bar: 123}} />
|
||||
);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo'}} />
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,13 +20,20 @@
|
||||
"use strict";
|
||||
|
||||
var Props = require('ReactPropTypes');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
|
||||
function typeCheck(declaration, value) {
|
||||
var props = {};
|
||||
if (arguments.length > 1) {
|
||||
props.testProp = value;
|
||||
}
|
||||
return declaration.bind(null, props, 'testProp', 'testComponent');
|
||||
return declaration.bind(
|
||||
null,
|
||||
props,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
ReactPropTypeLocations.prop
|
||||
);
|
||||
}
|
||||
|
||||
describe('Primitive Types', function() {
|
||||
|
||||
@@ -92,7 +92,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
|
||||
delete instancesByReactID[id];
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, rootNode) {
|
||||
componentDidUpdate: function(prevProps, prevState, prevContext, rootNode) {
|
||||
if (this.props.checked != null) {
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
rootNode,
|
||||
|
||||
@@ -110,7 +110,7 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
|
||||
return textarea(props, this.state.initialValue);
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, rootNode) {
|
||||
componentDidUpdate: function(prevProps, prevState, prevContext, rootNode) {
|
||||
var value = this.getValue();
|
||||
if (value != null) {
|
||||
// Cast `value` to a string to ensure the value is set correctly. While
|
||||
|
||||
@@ -196,6 +196,22 @@ mergeInto(reactComponentExpect.prototype, {
|
||||
.toEqual(propNameToExpectedValue[propName]);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check a set of props are equal to a set of expected values - only works
|
||||
* with scalars.
|
||||
*/
|
||||
scalarContextEqual: function(contextNameToExpectedValue) {
|
||||
expect(this.instance()).toBeTruthy();
|
||||
for (var contextName in contextNameToExpectedValue) {
|
||||
if (!contextNameToExpectedValue.hasOwnProperty(contextName)) {
|
||||
continue;
|
||||
}
|
||||
expect(this.instance().context[contextName])
|
||||
.toEqual(contextNameToExpectedValue[contextName]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user