Fix #6114 - Calling setState inside getChildContext should warn
rainer oviir committed
Mar 4, 2016 at 23:20 UTC
9c1916dedbe7e4462e8a4f43aad9b4e6bdbbf5ee
5 files changed
+89
src/isomorphic/ReactDebugTool.js
+12
@@ -11,6 +11,7 @@
11
12
'use strict';
13
14
+var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool');
15
var warning = require('warning');
16
17
var eventHandlers = [];
@@ -48,6 +49,15 @@ var ReactDebugTool = {
49
}
50
}
51
},
52
+ onBeginProcessingChildContext() {
53
+ emitEvent('onBeginProcessingChildContext');
54
+ },
55
+ onEndProcessingChildContext() {
56
+ emitEvent('onEndProcessingChildContext');
57
+ },
58
+ onSetState() {
59
+ emitEvent('onSetState');
60
+ },
61
onMountRootComponent(internalInstance) {
62
emitEvent('onMountRootComponent', internalInstance);
63
},
@@ -62,4 +72,6 @@ var ReactDebugTool = {
72
},
73
};
74
75
+ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool);
76
+
77
module.exports = ReactDebugTool;
src/isomorphic/devtools/ReactInvalidSetStateWarningDevTool.js
new
+39
@@ -0,0 +1,39 @@
1
+/**
2
+ * Copyright 2016-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactInvalidSetStateWarningDevTool
10
+ */
11
+
12
+'use strict';
13
+
14
+var warning = require('warning');
15
+
16
+if (__DEV__) {
17
+ var processingChildContext = false;
18
+
19
+ var warnInvalidSetState = function() {
20
+ warning(
21
+ !processingChildContext,
22
+ 'setState(...): Cannot call setState() inside getChildContext()'
23
+ );
24
+ };
25
+}
26
+
27
+var ReactInvalidSetStateWarningDevTool = {
28
+ onBeginProcessingChildContext() {
29
+ processingChildContext = true;
30
+ },
31
+ onEndProcessingChildContext() {
32
+ processingChildContext = false;
33
+ },
34
+ onSetState() {
35
+ warnInvalidSetState();
36
+ },
37
+};
38
+
39
+module.exports = ReactInvalidSetStateWarningDevTool;
src/isomorphic/modern/class/ReactComponent.js
+2
@@ -12,6 +12,7 @@
12
'use strict';
13
14
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
15
+var ReactInstrumentation = require('ReactInstrumentation');
16
17
var canDefineProperty = require('canDefineProperty');
18
var emptyObject = require('emptyObject');
@@ -66,6 +67,7 @@ ReactComponent.prototype.setState = function(partialState, callback) {
67
'function which returns an object of state variables.'
68
);
69
if (__DEV__) {
70
+ ReactInstrumentation.debugTool.onSetState();
71
warning(
72
partialState != null,
73
'setState(...): You passed an undefined or null state object; ' +
src/renderers/shared/reconciler/ReactCompositeComponent.js
+7
@@ -16,6 +16,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
16
var ReactElement = require('ReactElement');
17
var ReactErrorUtils = require('ReactErrorUtils');
18
var ReactInstanceMap = require('ReactInstanceMap');
19
+var ReactInstrumentation = require('ReactInstrumentation');
20
var ReactNodeTypes = require('ReactNodeTypes');
21
var ReactPerf = require('ReactPerf');
22
var ReactPropTypeLocations = require('ReactPropTypeLocations');
@@ -496,7 +497,13 @@ var ReactCompositeComponentMixin = {
497
_processChildContext: function(currentContext) {
498
var Component = this._currentElement.type;
499
var inst = this._instance;
500
+ if (__DEV__) {
501
+ ReactInstrumentation.debugTool.onBeginProcessingChildContext();
502
+ }
503
var childContext = inst.getChildContext && inst.getChildContext();
504
+ if (__DEV__) {
505
+ ReactInstrumentation.debugTool.onEndProcessingChildContext();
506
+ }
507
if (childContext) {
508
invariant(
509
typeof Component.childContextTypes === 'object',
src/renderers/shared/reconciler/__tests__/ReactCompositeComponent-test.js
+29
@@ -424,6 +424,35 @@ describe('ReactCompositeComponent', function() {
424
expect(instance2.state.value).toBe(1);
425
});
426
427
+ it('should warn about `setState` in getChildContext', function() {
428
+ var container = document.createElement('div');
429
+
430
+ var renderPasses = 0;
431
+
432
+ var Component = React.createClass({
433
+ getInitialState: function() {
434
+ return {value: 0};
435
+ },
436
+ getChildContext: function() {
437
+ if (this.state.value === 0) {
438
+ this.setState({ value: 1 });
439
+ }
440
+ },
441
+ render: function() {
442
+ renderPasses++;
443
+ return <div />;
444
+ },
445
+ });
446
+ expect(console.error.calls.length).toBe(0);
447
+ var instance = ReactDOM.render(<Component />, container);
448
+ expect(renderPasses).toBe(2);
449
+ expect(instance.state.value).toBe(1);
450
+ expect(console.error.calls.length).toBe(1);
451
+ expect(console.error.argsForCall[0][0]).toBe(
452
+ 'Warning: setState(...): Cannot call setState() inside getChildContext()'
453
+ );
454
+ });
455
+
456
it('should cleanup even if render() fatals', function() {
457
var BadComponent = React.createClass({
458
render: function() {