diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 2435a613f4006..4d2c2a3536a5e 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -11,6 +11,7 @@ 'use strict'; +var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool'); var warning = require('warning'); var eventHandlers = []; @@ -48,6 +49,15 @@ var ReactDebugTool = { } } }, + onBeginProcessingChildContext() { + emitEvent('onBeginProcessingChildContext'); + }, + onEndProcessingChildContext() { + emitEvent('onEndProcessingChildContext'); + }, + onSetState() { + emitEvent('onSetState'); + }, onMountRootComponent(internalInstance) { emitEvent('onMountRootComponent', internalInstance); }, @@ -62,4 +72,6 @@ var ReactDebugTool = { }, }; +ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool); + module.exports = ReactDebugTool; diff --git a/src/isomorphic/devtools/ReactInvalidSetStateWarningDevTool.js b/src/isomorphic/devtools/ReactInvalidSetStateWarningDevTool.js new file mode 100644 index 0000000000000..74275187cb323 --- /dev/null +++ b/src/isomorphic/devtools/ReactInvalidSetStateWarningDevTool.js @@ -0,0 +1,39 @@ +/** + * Copyright 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactInvalidSetStateWarningDevTool + */ + +'use strict'; + +var warning = require('warning'); + +if (__DEV__) { + var processingChildContext = false; + + var warnInvalidSetState = function() { + warning( + !processingChildContext, + 'setState(...): Cannot call setState() inside getChildContext()' + ); + }; +} + +var ReactInvalidSetStateWarningDevTool = { + onBeginProcessingChildContext() { + processingChildContext = true; + }, + onEndProcessingChildContext() { + processingChildContext = false; + }, + onSetState() { + warnInvalidSetState(); + }, +}; + +module.exports = ReactInvalidSetStateWarningDevTool; diff --git a/src/isomorphic/modern/class/ReactComponent.js b/src/isomorphic/modern/class/ReactComponent.js index 2882b56602d8c..a7d8b4baa92c8 100644 --- a/src/isomorphic/modern/class/ReactComponent.js +++ b/src/isomorphic/modern/class/ReactComponent.js @@ -12,6 +12,7 @@ 'use strict'; var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); +var ReactInstrumentation = require('ReactInstrumentation'); var canDefineProperty = require('canDefineProperty'); var emptyObject = require('emptyObject'); @@ -66,6 +67,7 @@ ReactComponent.prototype.setState = function(partialState, callback) { 'function which returns an object of state variables.' ); if (__DEV__) { + ReactInstrumentation.debugTool.onSetState(); warning( partialState != null, 'setState(...): You passed an undefined or null state object; ' + diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 68fd901bd90d4..9e636294b55d8 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -16,6 +16,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); var ReactErrorUtils = require('ReactErrorUtils'); var ReactInstanceMap = require('ReactInstanceMap'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactNodeTypes = require('ReactNodeTypes'); var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); @@ -496,7 +497,13 @@ var ReactCompositeComponentMixin = { _processChildContext: function(currentContext) { var Component = this._currentElement.type; var inst = this._instance; + if (__DEV__) { + ReactInstrumentation.debugTool.onBeginProcessingChildContext(); + } var childContext = inst.getChildContext && inst.getChildContext(); + if (__DEV__) { + ReactInstrumentation.debugTool.onEndProcessingChildContext(); + } if (childContext) { invariant( typeof Component.childContextTypes === 'object', diff --git a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js index 456b01325851f..9d5b06229315f 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js @@ -424,6 +424,35 @@ describe('ReactCompositeComponent', function() { expect(instance2.state.value).toBe(1); }); + it('should warn about `setState` in getChildContext', function() { + var container = document.createElement('div'); + + var renderPasses = 0; + + var Component = React.createClass({ + getInitialState: function() { + return {value: 0}; + }, + getChildContext: function() { + if (this.state.value === 0) { + this.setState({ value: 1 }); + } + }, + render: function() { + renderPasses++; + return
; + }, + }); + expect(console.error.calls.length).toBe(0); + var instance = ReactDOM.render(, container); + expect(renderPasses).toBe(2); + expect(instance.state.value).toBe(1); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toBe( + 'Warning: setState(...): Cannot call setState() inside getChildContext()' + ); + }); + it('should cleanup even if render() fatals', function() { var BadComponent = React.createClass({ render: function() {