diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt
index 0097217ec26..b2952dfe3d3 100644
--- a/scripts/fiber/tests-passing.txt
+++ b/scripts/fiber/tests-passing.txt
@@ -157,6 +157,8 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should pass next context to lifecycles
* should check context types
* should check child context types
+* should warn (but not error) if getChildContext method is missing
+* should pass parent context if getChildContext method is missing
src/isomorphic/classic/class/__tests__/ReactBind-test.js
* Holds reference to instance
diff --git a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js
index 7caedcd761b..08582a200dd 100644
--- a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js
+++ b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js
@@ -289,4 +289,91 @@ describe('ReactContextValidator', () => {
expectDev(console.error.calls.count()).toBe(2);
});
+ // TODO (bvaughn) Remove this test and the associated behavior in the future.
+ // It has only been added in Fiber to match the (unintentional) behavior in Stack.
+ it('should warn (but not error) if getChildContext method is missing', () => {
+ spyOn(console, 'error');
+
+ class ComponentA extends React.Component {
+ static childContextTypes = {
+ foo: React.PropTypes.string.isRequired,
+ };
+ render() {
+ return
;
+ }
+ }
+ class ComponentB extends React.Component {
+ static childContextTypes = {
+ foo: React.PropTypes.string.isRequired,
+ };
+ render() {
+ return ;
+ }
+ }
+
+ ReactTestUtils.renderIntoDocument();
+ expectDev(console.error.calls.count()).toBe(1);
+ expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
+ 'Warning: ComponentA.childContextTypes is specified but there is no ' +
+ 'getChildContext() method on the instance. You can either define ' +
+ 'getChildContext() on ComponentA or remove childContextTypes from it.'
+ );
+
+ // Warnings should be deduped by component type
+ ReactTestUtils.renderIntoDocument();
+ expectDev(console.error.calls.count()).toBe(1);
+ ReactTestUtils.renderIntoDocument();
+ expectDev(console.error.calls.count()).toBe(2);
+ expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
+ 'Warning: ComponentB.childContextTypes is specified but there is no ' +
+ 'getChildContext() method on the instance. You can either define ' +
+ 'getChildContext() on ComponentB or remove childContextTypes from it.'
+ );
+ });
+
+ // TODO (bvaughn) Remove this test and the associated behavior in the future.
+ // It has only been added in Fiber to match the (unintentional) behavior in Stack.
+ it('should pass parent context if getChildContext method is missing', () => {
+ spyOn(console, 'error');
+
+ class ParentContextProvider extends React.Component {
+ static childContextTypes = {
+ foo: React.PropTypes.number,
+ };
+ getChildContext() {
+ return {
+ foo: 'FOO',
+ };
+ }
+ render() {
+ return ;
+ }
+ }
+
+ class MiddleMissingContext extends React.Component {
+ static childContextTypes = {
+ bar: React.PropTypes.string.isRequired,
+ };
+ render() {
+ return ;
+ }
+ }
+
+ var childContext;
+ var ChildContextConsumer = React.createClass({
+ contextTypes: {
+ bar: React.PropTypes.string.isRequired,
+ foo: React.PropTypes.string.isRequired,
+ },
+ render: function() {
+ childContext = this.context;
+ return ;
+ },
+ });
+
+ ReactTestUtils.renderIntoDocument();
+ expect(childContext.bar).toBeUndefined();
+ expect(childContext.foo).toBe('FOO');
+ });
+
});
diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js
index 1746a6e8183..322affe797d 100644
--- a/src/renderers/shared/fiber/ReactFiberContext.js
+++ b/src/renderers/shared/fiber/ReactFiberContext.js
@@ -17,6 +17,7 @@ import type { StackCursor } from 'ReactFiberStack';
var emptyObject = require('emptyObject');
var invariant = require('invariant');
+var warning = require('warning');
var {
getComponentName,
isFiberMounted,
@@ -33,6 +34,7 @@ const {
if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec');
+ var warnedAboutMissingGetChildContext = {};
}
// A cursor to the current merged context object on the stack.
@@ -141,6 +143,28 @@ exports.pushTopLevelContextObject = function(fiber : Fiber, context : Object, di
function processChildContext(fiber : Fiber, parentContext : Object, isReconciling : boolean): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
+
+ // TODO (bvaughn) Replace this behavior with an invariant() in the future.
+ // It has only been added in Fiber to match the (unintentional) behavior in Stack.
+ if (typeof instance.getChildContext !== 'function') {
+ if (__DEV__) {
+ const componentName = getComponentName(fiber);
+
+ if (!warnedAboutMissingGetChildContext[componentName]) {
+ warnedAboutMissingGetChildContext[componentName] = true;
+ warning(
+ false,
+ '%s.childContextTypes is specified but there is no getChildContext() method ' +
+ 'on the instance. You can either define getChildContext() on %s or remove ' +
+ 'childContextTypes from it.',
+ componentName,
+ componentName,
+ );
+ }
+ }
+ return parentContext;
+ }
+
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
diff --git a/src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js b/src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js
index 876f2685548..b31706c66b5 100644
--- a/src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js
+++ b/src/renderers/shared/shared/__tests__/ReactStatelessComponent-test.js
@@ -118,11 +118,17 @@ describe('ReactStatelessComponent', () => {
ReactDOM.render(, container);
- expectDev(console.error.calls.count()).toBe(1);
+ expectDev(console.error.calls.count()).toBe(2);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
'be defined on a functional component.'
);
+ expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
+ 'Warning: StatelessComponentWithChildContext.childContextTypes is specified ' +
+ 'but there is no getChildContext() method on the instance. You can either ' +
+ 'define getChildContext() on StatelessComponentWithChildContext or remove ' +
+ 'childContextTypes from it.'
+ );
});
if (!ReactDOMFeatureFlags.useFiber) {
diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
index ecdccd09824..e7aa14ef93a 100644
--- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
@@ -24,6 +24,7 @@ var ReactReconciler = require('ReactReconciler');
if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec');
+ var warningAboutMissingGetChildContext = {};
}
var emptyObject = require('emptyObject');
@@ -698,6 +699,22 @@ var ReactCompositeComponent = {
);
}
return Object.assign({}, currentContext, childContext);
+ } else {
+ if (__DEV__) {
+ const componentName = this.getName();
+
+ if (!warningAboutMissingGetChildContext[componentName]) {
+ warningAboutMissingGetChildContext[componentName] = true;
+ warning(
+ !Component.childContextTypes,
+ '%s.childContextTypes is specified but there is no getChildContext() method ' +
+ 'on the instance. You can either define getChildContext() on %s or remove ' +
+ 'childContextTypes from it.',
+ componentName,
+ componentName,
+ );
+ }
+ }
}
return currentContext;
},