@samitouri / QOS-React / commits / 3f1f3dc12e

Deduplicated many warnings (#11140) (#11216)

Deduplicated many warnings (#11140) (#11216) * Deduplicated many warnings (#11140) * Deduplicated the following warnings: 1. Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op 2. %s.componentWillReceiveProps(): Assigning directly to this.state is deprecated (except inside a component's constructor). Use setState instead.' 3. An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using componentDidUpdate or a callback. 4. setState(...): Cannot call setState() inside getChildContext() * Code review changes made for #11140 * Minor style fix * Test deduplication for noop updates in server renderer * Test deduplication for cWRP warning * Test deduplication for cWM setState warning * Test deduplication for unnmounted setState warning * Fix existing Flow typing * Test deduplication for invalid updates * Test deduplication of update-in-updater warning

Anushree Subramani committed Oct 31, 2017 at 18:05 UTC 3f1f3dc12e72809c997d8e1078edfadd7e31bc14
10 files changed +96 -24
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+4
@@ -221,6 +221,10 @@ describe('ReactComponentLifeCycle', () => {
221 'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
222 'StatefulComponent component.',
223 );
224 +
225 + // Check deduplication
226 + ReactTestUtils.renderIntoDocument(<StatefulComponent />);
227 + expectDev(console.error.calls.count()).toBe(1);
228 });
229
230 it('should correctly determine if a component is mounted', () => {
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+13
@@ -252,6 +252,9 @@ describe('ReactCompositeComponent', () => {
252 'component. This is a no-op.\n\nPlease check the code for the ' +
253 'Component component.',
254 );
255 +
256 + instance.forceUpdate();
257 + expectDev(console.error.calls.count()).toBe(1);
258 });
259
260 it('should warn about `setState` on unmounted components', () => {
@@ -391,6 +394,11 @@ describe('ReactCompositeComponent', () => {
394 expect(instance).toBe(instance2);
395 expect(renderedState).toBe(1);
396 expect(instance2.state.value).toBe(1);
397 +
398 + // Test deduplication
399 + ReactDOM.unmountComponentAtNode(container);
400 + ReactDOM.render(<Component prop={123} />, container);
401 + expectDev(console.error.calls.count()).toBe(1);
402 });
403
404 it('should warn about `setState` in getChildContext', () => {
@@ -424,6 +432,11 @@ describe('ReactCompositeComponent', () => {
432 expectDev(console.error.calls.argsFor(0)[0]).toBe(
433 'Warning: setState(...): Cannot call setState() inside getChildContext()',
434 );
435 +
436 + // Test deduplication
437 + ReactDOM.unmountComponentAtNode(container);
438 + ReactDOM.render(<Component />, container);
439 + expectDev(console.error.calls.count()).toBe(1);
440 });
441
442 it('should cleanup even if render() fatals', () => {
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+4
@@ -421,6 +421,10 @@ describe('ReactCompositeComponent-state', () => {
421 "this.state is deprecated (except inside a component's constructor). " +
422 'Use setState instead.',
423 );
424 +
425 + // Check deduplication
426 + ReactDOM.render(<Test />, container);
427 + expect(console.error.calls.count()).toEqual(1);
428 });
429
430 it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+3
@@ -656,8 +656,11 @@ describe('ReactDOMServer', () => {
656 ' This usually means you called setState() outside componentWillMount() on the server.' +
657 ' This is a no-op.\n\nPlease check the code for the Foo component.',
658 );
659 +
660 var markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
661 expect(markup).toBe('<div>hello</div>');
662 + jest.runOnlyPendingTimers();
663 + expectDev(console.error.calls.count()).toBe(1);
664 });
665
666 it('warns with a no-op when an async forceUpdate is triggered', () => {
packages/react-dom/src/server/ReactPartialRenderer.js
+10 -1
@@ -118,6 +118,7 @@ var didWarnDefaultChecked = false;
118 var didWarnDefaultSelectValue = false;
119 var didWarnDefaultTextareaValue = false;
120 var didWarnInvalidOptionChildren = false;
121 +var didWarnAboutNoopUpdateForComponent = {};
122 var valuePropNames = ['value', 'defaultValue'];
123 var newlineEatingTags = {
124 listing: true,
@@ -181,6 +182,13 @@ function warnNoop(
182 ) {
183 if (__DEV__) {
184 var constructor = publicInstance.constructor;
185 + const componentName =
186 + (constructor && getComponentName(constructor)) || 'ReactClass';
187 + const warningKey = `${componentName}.${callerName}`;
188 + if (didWarnAboutNoopUpdateForComponent[warningKey]) {
189 + return;
190 + }
191 +
192 warning(
193 false,
194 '%s(...): Can only update a mounting component. ' +
@@ -188,8 +196,9 @@ function warnNoop(
196 'This is a no-op.\n\nPlease check the code for the %s component.',
197 callerName,
198 callerName,
191 - (constructor && getComponentName(constructor)) || 'ReactClass',
199 + componentName,
200 );
201 + didWarnAboutNoopUpdateForComponent[warningKey] = true;
202 }
203 }
204
packages/react-reconciler/src/ReactFiberClassComponent.js
+12 -7
@@ -41,6 +41,7 @@ if (__DEV__) {
41 var warning = require('fbjs/lib/warning');
42
43 var {startPhaseTimer, stopPhaseTimer} = require('./ReactDebugFiberPerf');
44 + var didWarnAboutStateAssignmentForComponent = {};
45
46 var warnOnInvalidCallback = function(callback: mixed, callerName: string) {
47 warning(
@@ -393,13 +394,17 @@ module.exports = function(
394
395 if (instance.state !== oldState) {
396 if (__DEV__) {
396 - warning(
397 - false,
398 - '%s.componentWillReceiveProps(): Assigning directly to ' +
399 - "this.state is deprecated (except inside a component's " +
400 - 'constructor). Use setState instead.',
401 - getComponentName(workInProgress),
402 - );
397 + const componentName = getComponentName(workInProgress) || 'Component';
398 + if (!didWarnAboutStateAssignmentForComponent[componentName]) {
399 + warning(
400 + false,
401 + '%s.componentWillReceiveProps(): Assigning directly to ' +
402 + "this.state is deprecated (except inside a component's " +
403 + 'constructor). Use setState instead.',
404 + componentName,
405 + );
406 + didWarnAboutStateAssignmentForComponent[componentName] = true;
407 + }
408 }
409 updater.enqueueReplaceState(instance, instance.state, null);
410 }
packages/react-reconciler/src/ReactFiberScheduler.js
+20 -12
@@ -101,34 +101,41 @@ if (__DEV__) {
101 } = require('./ReactDebugFiberPerf');
102
103 var didWarnAboutStateTransition = false;
104 + var didWarnSetStateChildContext = false;
105 + var didWarnStateUpdateForUnmountedComponent = {};
106
105 - var warnAboutUpdateOnUnmounted = function(
106 - instance: React$ComponentType<any>,
107 - ) {
108 - const ctor = instance.constructor;
107 + var warnAboutUpdateOnUnmounted = function(fiber: Fiber) {
108 + const componentName = getComponentName(fiber) || 'ReactClass';
109 + if (didWarnStateUpdateForUnmountedComponent[componentName]) {
110 + return;
111 + }
112 warning(
113 false,
111 - 'Can only update a mounted or mounting component. This usually means ' +
112 - 'you called setState, replaceState, or forceUpdate on an unmounted ' +
113 - 'component. This is a no-op.\n\nPlease check the code for the ' +
114 - '%s component.',
115 - (ctor && (ctor.displayName || ctor.name)) || 'ReactClass',
114 + 'Can only update a mounted or mounting ' +
115 + 'component. This usually means you called setState, replaceState, ' +
116 + 'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' +
117 + 'check the code for the %s component.',
118 + componentName,
119 );
120 + didWarnStateUpdateForUnmountedComponent[componentName] = true;
121 };
122
119 - var warnAboutInvalidUpdates = function(instance: React$ComponentType<any>) {
123 + var warnAboutInvalidUpdates = function(instance: React$Component<any>) {
124 switch (ReactDebugCurrentFiber.phase) {
125 case 'getChildContext':
126 + if (didWarnSetStateChildContext) {
127 + return;
128 + }
129 warning(
130 false,
131 'setState(...): Cannot call setState() inside getChildContext()',
132 );
133 + didWarnSetStateChildContext = true;
134 break;
135 case 'render':
136 if (didWarnAboutStateTransition) {
137 return;
138 }
131 - didWarnAboutStateTransition = true;
139 warning(
140 false,
141 'Cannot update during an existing state transition (such as within ' +
@@ -136,6 +143,7 @@ if (__DEV__) {
143 'be a pure function of props and state; constructor side-effects are ' +
144 'an anti-pattern, but can be moved to `componentWillMount`.',
145 );
146 + didWarnAboutStateTransition = true;
147 break;
148 }
149 };
@@ -1229,7 +1237,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1237 } else {
1238 if (__DEV__) {
1239 if (!isErrorRecovery && fiber.tag === ClassComponent) {
1232 - warnAboutUpdateOnUnmounted(fiber.stateNode);
1240 + warnAboutUpdateOnUnmounted(fiber);
1241 }
1242 }
1243 return;
packages/react-reconciler/src/ReactFiberUpdateQueue.js
+6 -1
@@ -20,6 +20,7 @@ const {NoWork} = require('./ReactFiberExpirationTime');
20
21 if (__DEV__) {
22 var warning = require('fbjs/lib/warning');
23 + var didWarnUpdateInsideUpdate = false;
24 }
25
26 type PartialState<State, Props> =
@@ -132,7 +133,10 @@ function insertUpdateIntoFiber<State>(
133
134 // Warn if an update is scheduled from inside an updater function.
135 if (__DEV__) {
135 - if (queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) {
136 + if (
137 + (queue1.isProcessing || (queue2 !== null && queue2.isProcessing)) &&
138 + !didWarnUpdateInsideUpdate
139 + ) {
140 warning(
141 false,
142 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
@@ -140,6 +144,7 @@ function insertUpdateIntoFiber<State>(
144 'with zero side-effects. Consider using componentDidUpdate or a ' +
145 'callback.',
146 );
147 + didWarnUpdateInsideUpdate = true;
148 }
149 }
150
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+14 -1
@@ -341,6 +341,19 @@ describe('ReactIncrementalUpdates', () => {
341 expect(instance.state).toEqual({a: 'a', b: 'b'});
342
343 expectDev(console.error.calls.count()).toBe(1);
344 - console.error.calls.reset();
344 + expectDev(console.error.calls.argsFor(0)[0]).toContain(
345 + 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
346 + 'from inside an update function. Update functions should be pure, ' +
347 + 'with zero side-effects. Consider using componentDidUpdate or a ' +
348 + 'callback.',
349 + );
350 +
351 + // Test deduplication
352 + instance.setState(function a() {
353 + this.setState({a: 'a'});
354 + return {b: 'b'};
355 + });
356 + ReactNoop.flush();
357 + expectDev(console.error.calls.count()).toBe(1);
358 });
359 });
packages/react/src/ReactNoopUpdateQueue.js
+10 -2
@@ -9,11 +9,19 @@
9
10 if (__DEV__) {
11 var warning = require('fbjs/lib/warning');
12 + var didWarnStateUpdateForUnmountedComponent = {};
13 }
14
15 function warnNoop(publicInstance, callerName) {
16 if (__DEV__) {
17 var constructor = publicInstance.constructor;
18 + const componentName =
19 + (constructor && (constructor.displayName || constructor.name)) ||
20 + 'ReactClass';
21 + const warningKey = `${componentName}.${callerName}`;
22 + if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
23 + return;
24 + }
25 warning(
26 false,
27 '%s(...): Can only update a mounted or mounting component. ' +
@@ -21,9 +29,9 @@ function warnNoop(publicInstance, callerName) {
29 'This is a no-op.\n\nPlease check the code for the %s component.',
30 callerName,
31 callerName,
24 - (constructor && (constructor.displayName || constructor.name)) ||
25 - 'ReactClass',
32 + componentName,
33 );
34 + didWarnStateUpdateForUnmountedComponent[warningKey] = true;
35 }
36 }
37