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');