@samitouri / QOS-React / commits / 6dabfca577

Coalesce lifecycle deprecation warnings until the commit phase (#12084)

Builds on top of PR #12083 and resolves issue #12044. Coalesces deprecation warnings until the commit phase. This proposal extends the utility introduced in #12060 to also coalesce deprecation warnings. New warning format will look like this: > componentWillMount is deprecated and will be removed in the next major version. Use componentDidMount instead. As a temporary workaround, you can rename to UNSAFE_componentWillMount. > > Please update the following components: Foo, Bar > > Learn more about this warning here: > https://fb.me/react-async-component-lifecycle-hooks

Brian Vaughn committed Jan 24, 2018 at 21:41 UTC 6dabfca5774b9b2ceaa1faf8ad225e6dac016c31
5 files changed +159 -103
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js
+13 -11
@@ -38,19 +38,21 @@ describe('ReactComponentLifeCycle', () => {
38
39 const container = document.createElement('div');
40 expect(() => ReactDOM.render(<MyComponent x={1} />, container)).toWarnDev([
41 - 'Warning: MyComponent: componentWillMount() is deprecated and will be ' +
42 - 'removed in the next major version.',
41 + 'componentWillMount is deprecated and will be removed in the next major version. ' +
42 + 'Use componentDidMount instead. As a temporary workaround, ' +
43 + 'you can rename to UNSAFE_componentWillMount.' +
44 + '\n\nPlease update the following components: MyComponent',
45 + 'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
46 + 'Use static getDerivedStateFromProps instead.' +
47 + '\n\nPlease update the following components: MyComponent',
48 + 'componentWillUpdate is deprecated and will be removed in the next major version. ' +
49 + 'Use componentDidUpdate instead. As a temporary workaround, ' +
50 + 'you can rename to UNSAFE_componentWillUpdate.' +
51 + '\n\nPlease update the following components: MyComponent',
52 ]);
53
45 - expect(() => ReactDOM.render(<MyComponent x={2} />, container)).toWarnDev([
46 - 'Warning: MyComponent: componentWillReceiveProps() is deprecated and ' +
47 - 'will be removed in the next major version.',
48 - 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' +
49 - 'removed in the next major version.',
50 - ]);
51 -
52 - // Dedupe check (instantiate and update)
54 + // Dedupe check (update and instantiate new
55 + ReactDOM.render(<MyComponent x={2} />, container);
56 ReactDOM.render(<MyComponent key="new" x={1} />, container);
54 - ReactDOM.render(<MyComponent key="new" x={2} />, container);
57 });
58 });
packages/react-reconciler/src/ReactFiberClassComponent.js
+8 -70
@@ -42,9 +42,6 @@ import {hasContextChanged} from './ReactFiberContext';
42 const fakeInternalInstance = {};
43 const isArray = Array.isArray;
44
45 -let didWarnAboutLegacyWillMount;
46 -let didWarnAboutLegacyWillReceiveProps;
47 -let didWarnAboutLegacyWillUpdate;
45 let didWarnAboutStateAssignmentForComponent;
46 let didWarnAboutUndefinedDerivedState;
47 let didWarnAboutUninitializedState;
@@ -52,11 +49,6 @@ let didWarnAboutWillReceivePropsAndDerivedState;
49 let warnOnInvalidCallback;
50
51 if (__DEV__) {
55 - if (warnAboutDeprecatedLifecycles) {
56 - didWarnAboutLegacyWillMount = {};
57 - didWarnAboutLegacyWillReceiveProps = {};
58 - didWarnAboutLegacyWillUpdate = {};
59 - }
52 didWarnAboutStateAssignmentForComponent = {};
53 didWarnAboutUndefinedDerivedState = {};
54 didWarnAboutUninitializedState = {};
@@ -462,25 +454,6 @@ export default function(
454 const oldState = instance.state;
455
456 if (typeof instance.componentWillMount === 'function') {
465 - if (__DEV__) {
466 - if (warnAboutDeprecatedLifecycles) {
467 - const componentName = getComponentName(workInProgress) || 'Component';
468 - if (!didWarnAboutLegacyWillMount[componentName]) {
469 - warning(
470 - false,
471 - '%s: componentWillMount() is deprecated and will be ' +
472 - 'removed in the next major version. Read about the motivations ' +
473 - 'behind this change: ' +
474 - 'https://fb.me/react-async-component-lifecycle-hooks' +
475 - '\n\n' +
476 - 'As a temporary workaround, you can rename to ' +
477 - 'UNSAFE_componentWillMount instead.',
478 - componentName,
479 - );
480 - didWarnAboutLegacyWillMount[componentName] = true;
481 - }
482 - }
483 - }
457 instance.componentWillMount();
458 } else {
459 instance.UNSAFE_componentWillMount();
@@ -510,27 +483,6 @@ export default function(
483 ) {
484 const oldState = instance.state;
485 if (typeof instance.componentWillReceiveProps === 'function') {
513 - if (__DEV__) {
514 - if (warnAboutDeprecatedLifecycles) {
515 - const componentName = getComponentName(workInProgress) || 'Component';
516 - if (!didWarnAboutLegacyWillReceiveProps[componentName]) {
517 - warning(
518 - false,
519 - '%s: componentWillReceiveProps() is deprecated and ' +
520 - 'will be removed in the next major version. Use ' +
521 - 'static getDerivedStateFromProps() instead. Read about the ' +
522 - 'motivations behind this change: ' +
523 - 'https://fb.me/react-async-component-lifecycle-hooks' +
524 - '\n\n' +
525 - 'As a temporary workaround, you can rename to ' +
526 - 'UNSAFE_componentWillReceiveProps instead.',
527 - componentName,
528 - );
529 - didWarnAboutLegacyWillReceiveProps[componentName] = true;
530 - }
531 - }
532 - }
533 -
486 startPhaseTimer(workInProgress, 'componentWillReceiveProps');
487 instance.componentWillReceiveProps(newProps, newContext);
488 stopPhaseTimer();
@@ -652,7 +604,14 @@ export default function(
604
605 if (__DEV__) {
606 if (workInProgress.internalContextTag & StrictMode) {
655 - ReactStrictModeWarnings.recordLifecycleWarnings(
607 + ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
608 + workInProgress,
609 + instance,
610 + );
611 + }
612 +
613 + if (warnAboutDeprecatedLifecycles) {
614 + ReactStrictModeWarnings.recordDeprecationWarnings(
615 workInProgress,
616 instance,
617 );
@@ -893,27 +852,6 @@ export default function(
852 typeof instance.componentWillUpdate === 'function'
853 ) {
854 if (typeof instance.componentWillUpdate === 'function') {
896 - if (__DEV__) {
897 - if (warnAboutDeprecatedLifecycles) {
898 - const componentName =
899 - getComponentName(workInProgress) || 'Component';
900 - if (!didWarnAboutLegacyWillUpdate[componentName]) {
901 - warning(
902 - false,
903 - '%s: componentWillUpdate() is deprecated and will be ' +
904 - 'removed in the next major version. Read about the motivations ' +
905 - 'behind this change: ' +
906 - 'https://fb.me/react-async-component-lifecycle-hooks' +
907 - '\n\n' +
908 - 'As a temporary workaround, you can rename to ' +
909 - 'UNSAFE_componentWillUpdate instead.',
910 - componentName,
911 - );
912 - didWarnAboutLegacyWillUpdate[componentName] = true;
913 - }
914 - }
915 - }
916 -
855 startPhaseTimer(workInProgress, 'componentWillUpdate');
856 instance.componentWillUpdate(newProps, newState, newContext);
857 stopPhaseTimer();
packages/react-reconciler/src/ReactFiberScheduler.js
+9 -2
@@ -35,7 +35,10 @@ import {
35 ClassComponent,
36 ContextProvider,
37 } from 'shared/ReactTypeOfWork';
38 -import {enableUserTimingAPI} from 'shared/ReactFeatureFlags';
38 +import {
39 + enableUserTimingAPI,
40 + warnAboutDeprecatedLifecycles,
41 +} from 'shared/ReactFeatureFlags';
42 import getComponentName from 'shared/getComponentName';
43 import invariant from 'fbjs/lib/invariant';
44 import warning from 'fbjs/lib/warning';
@@ -316,7 +319,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
319
320 function commitAllLifeCycles() {
321 if (__DEV__) {
319 - ReactStrictModeWarnings.flushPendingAsyncWarnings();
322 + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
323 +
324 + if (warnAboutDeprecatedLifecycles) {
325 + ReactStrictModeWarnings.flushPendingDeprecationWarnings();
326 + }
327 }
328
329 while (nextEffect !== null) {
packages/react-reconciler/src/ReactStrictModeWarnings.js
+119 -14
@@ -23,8 +23,10 @@ type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
23
24 const ReactStrictModeWarnings = {
25 discardPendingWarnings(): void {},
26 - flushPendingAsyncWarnings(): void {},
27 - recordLifecycleWarnings(fiber: Fiber, instance: any): void {},
26 + flushPendingDeprecationWarnings(): void {},
27 + flushPendingUnsafeLifecycleWarnings(): void {},
28 + recordDeprecationWarnings(fiber: Fiber, instance: any): void {},
29 + recordUnsafeLifecycleWarnings(fiber: Fiber, instance: any): void {},
30 };
31
32 if (__DEV__) {
@@ -34,17 +36,24 @@ if (__DEV__) {
36 UNSAFE_componentWillUpdate: 'componentDidUpdate',
37 };
38
37 - let pendingWarningsMap: FiberToLifecycleMap = new Map();
39 + let pendingComponentWillMountWarnings: Array<Fiber> = [];
40 + let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
41 + let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
42 + let pendingUnsafeLifecycleWarnings: FiberToLifecycleMap = new Map();
43
44 // Tracks components we have already warned about.
40 - const didWarnSet = new Set();
45 + const didWarnAboutDeprecatedLifecycles = new Set();
46 + const didWarnAboutUnsafeLifecycles = new Set();
47
48 ReactStrictModeWarnings.discardPendingWarnings = () => {
43 - pendingWarningsMap = new Map();
49 + pendingComponentWillMountWarnings = [];
50 + pendingComponentWillReceivePropsWarnings = [];
51 + pendingComponentWillUpdateWarnings = [];
52 + pendingUnsafeLifecycleWarnings = new Map();
53 };
54
46 - ReactStrictModeWarnings.flushPendingAsyncWarnings = () => {
47 - ((pendingWarningsMap: any): FiberToLifecycleMap).forEach(
55 + ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
56 + ((pendingUnsafeLifecycleWarnings: any): FiberToLifecycleMap).forEach(
57 (lifecycleWarningsMap, strictRoot) => {
58 const lifecyclesWarningMesages = [];
59
@@ -54,7 +63,7 @@ if (__DEV__) {
63 const componentNames = new Set();
64 lifecycleWarnings.forEach(fiber => {
65 componentNames.add(getComponentName(fiber) || 'Component');
57 - didWarnSet.add(fiber.type);
66 + didWarnAboutUnsafeLifecycles.add(fiber.type);
67 });
68
69 const formatted = lifecycle.replace('UNSAFE_', '');
@@ -88,7 +97,7 @@ if (__DEV__) {
97 },
98 );
99
91 - pendingWarningsMap = new Map();
100 + pendingUnsafeLifecycleWarnings = new Map();
101 };
102
103 const getStrictRoot = (fiber: Fiber): Fiber => {
@@ -105,7 +114,103 @@ if (__DEV__) {
114 return maybeStrictRoot;
115 };
116
108 - ReactStrictModeWarnings.recordLifecycleWarnings = (
117 + ReactStrictModeWarnings.flushPendingDeprecationWarnings = () => {
118 + if (pendingComponentWillMountWarnings.length > 0) {
119 + const uniqueNames = new Set();
120 + pendingComponentWillMountWarnings.forEach(fiber => {
121 + uniqueNames.add(getComponentName(fiber) || 'Component');
122 + didWarnAboutDeprecatedLifecycles.add(fiber.type);
123 + });
124 +
125 + const sortedNames = Array.from(uniqueNames)
126 + .sort()
127 + .join(', ');
128 +
129 + warning(
130 + false,
131 + 'componentWillMount is deprecated and will be removed in the next major version. ' +
132 + 'Use componentDidMount instead. As a temporary workaround, ' +
133 + 'you can rename to UNSAFE_componentWillMount.' +
134 + '\n\nPlease update the following components: %s' +
135 + '\n\nLearn more about this warning here:' +
136 + '\nhttps://fb.me/react-async-component-lifecycle-hooks',
137 + sortedNames,
138 + );
139 +
140 + pendingComponentWillMountWarnings = [];
141 + }
142 +
143 + if (pendingComponentWillReceivePropsWarnings.length > 0) {
144 + const uniqueNames = new Set();
145 + pendingComponentWillReceivePropsWarnings.forEach(fiber => {
146 + uniqueNames.add(getComponentName(fiber) || 'Component');
147 + didWarnAboutDeprecatedLifecycles.add(fiber.type);
148 + });
149 +
150 + const sortedNames = Array.from(uniqueNames)
151 + .sort()
152 + .join(', ');
153 +
154 + warning(
155 + false,
156 + 'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
157 + 'Use static getDerivedStateFromProps instead.' +
158 + '\n\nPlease update the following components: %s' +
159 + '\n\nLearn more about this warning here:' +
160 + '\nhttps://fb.me/react-async-component-lifecycle-hooks',
161 + sortedNames,
162 + );
163 +
164 + pendingComponentWillReceivePropsWarnings = [];
165 + }
166 +
167 + if (pendingComponentWillUpdateWarnings.length > 0) {
168 + const uniqueNames = new Set();
169 + pendingComponentWillUpdateWarnings.forEach(fiber => {
170 + uniqueNames.add(getComponentName(fiber) || 'Component');
171 + didWarnAboutDeprecatedLifecycles.add(fiber.type);
172 + });
173 +
174 + const sortedNames = Array.from(uniqueNames)
175 + .sort()
176 + .join(', ');
177 +
178 + warning(
179 + false,
180 + 'componentWillUpdate is deprecated and will be removed in the next major version. ' +
181 + 'Use componentDidUpdate instead. As a temporary workaround, ' +
182 + 'you can rename to UNSAFE_componentWillUpdate.' +
183 + '\n\nPlease update the following components: %s' +
184 + '\n\nLearn more about this warning here:' +
185 + '\nhttps://fb.me/react-async-component-lifecycle-hooks',
186 + sortedNames,
187 + );
188 +
189 + pendingComponentWillUpdateWarnings = [];
190 + }
191 + };
192 +
193 + ReactStrictModeWarnings.recordDeprecationWarnings = (
194 + fiber: Fiber,
195 + instance: any,
196 + ) => {
197 + // Dedup strategy: Warn once per component.
198 + if (didWarnAboutDeprecatedLifecycles.has(fiber.type)) {
199 + return;
200 + }
201 +
202 + if (typeof instance.componentWillMount === 'function') {
203 + pendingComponentWillMountWarnings.push(fiber);
204 + }
205 + if (typeof instance.componentWillReceiveProps === 'function') {
206 + pendingComponentWillReceivePropsWarnings.push(fiber);
207 + }
208 + if (typeof instance.componentWillUpdate === 'function') {
209 + pendingComponentWillUpdateWarnings.push(fiber);
210 + }
211 + };
212 +
213 + ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = (
214 fiber: Fiber,
215 instance: any,
216 ) => {
@@ -116,21 +221,21 @@ if (__DEV__) {
221 // are often vague and are likely to collide between 3rd party libraries.
222 // An expand property is probably okay to use here since it's DEV-only,
223 // and will only be set in the event of serious warnings.
119 - if (didWarnSet.has(fiber.type)) {
224 + if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
225 return;
226 }
227
228 let warningsForRoot;
124 - if (!pendingWarningsMap.has(strictRoot)) {
229 + if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) {
230 warningsForRoot = {
231 UNSAFE_componentWillMount: [],
232 UNSAFE_componentWillReceiveProps: [],
233 UNSAFE_componentWillUpdate: [],
234 };
235
131 - pendingWarningsMap.set(strictRoot, warningsForRoot);
236 + pendingUnsafeLifecycleWarnings.set(strictRoot, warningsForRoot);
237 } else {
133 - warningsForRoot = pendingWarningsMap.get(strictRoot);
238 + warningsForRoot = pendingUnsafeLifecycleWarnings.get(strictRoot);
239 }
240
241 const unsafeLifecycles = [];
packages/react/src/__tests__/createReactClassIntegration-test.internal.js
+10 -6
@@ -95,14 +95,18 @@ describe('create-react-class-integration', () => {
95 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
96 'clean up subscriptions and pending requests in componentWillUnmount ' +
97 'to prevent memory leaks.',
98 - 'Warning: MyComponent: componentWillMount() is deprecated and will be ' +
99 - 'removed in the next major version.',
98 + 'componentWillMount is deprecated and will be removed in the next major version. ' +
99 + 'Use componentDidMount instead. As a temporary workaround, ' +
100 + 'you can rename to UNSAFE_componentWillMount.' +
101 + '\n\nPlease update the following components: MyComponent',
102 + 'componentWillUpdate is deprecated and will be removed in the next major version. ' +
103 + 'Use componentDidUpdate instead. As a temporary workaround, ' +
104 + 'you can rename to UNSAFE_componentWillUpdate.' +
105 + '\n\nPlease update the following components: MyComponent',
106 ]);
107
102 - expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
103 - 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' +
104 - 'removed in the next major version.',
105 - );
108 + // Dedupe
109 + ReactDOM.render(<Component />, container);
110
111 ReactDOM.unmountComponentAtNode(container);
112 instance.log('after unmount');