Skip to content

Commit 6dabfca

Browse files
authored
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
1 parent 87ae211 commit 6dabfca

File tree

5 files changed

+159
-103
lines changed

5 files changed

+159
-103
lines changed

packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js

+13-11
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,21 @@ describe('ReactComponentLifeCycle', () => {
3838

3939
const container = document.createElement('div');
4040
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',
4352
]);
4453

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);
5356
ReactDOM.render(<MyComponent key="new" x={1} />, container);
54-
ReactDOM.render(<MyComponent key="new" x={2} />, container);
5557
});
5658
});

packages/react-reconciler/src/ReactFiberClassComponent.js

+8-70
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,13 @@ import {hasContextChanged} from './ReactFiberContext';
4242
const fakeInternalInstance = {};
4343
const isArray = Array.isArray;
4444

45-
let didWarnAboutLegacyWillMount;
46-
let didWarnAboutLegacyWillReceiveProps;
47-
let didWarnAboutLegacyWillUpdate;
4845
let didWarnAboutStateAssignmentForComponent;
4946
let didWarnAboutUndefinedDerivedState;
5047
let didWarnAboutUninitializedState;
5148
let didWarnAboutWillReceivePropsAndDerivedState;
5249
let warnOnInvalidCallback;
5350

5451
if (__DEV__) {
55-
if (warnAboutDeprecatedLifecycles) {
56-
didWarnAboutLegacyWillMount = {};
57-
didWarnAboutLegacyWillReceiveProps = {};
58-
didWarnAboutLegacyWillUpdate = {};
59-
}
6052
didWarnAboutStateAssignmentForComponent = {};
6153
didWarnAboutUndefinedDerivedState = {};
6254
didWarnAboutUninitializedState = {};
@@ -462,25 +454,6 @@ export default function(
462454
const oldState = instance.state;
463455

464456
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-
}
484457
instance.componentWillMount();
485458
} else {
486459
instance.UNSAFE_componentWillMount();
@@ -510,27 +483,6 @@ export default function(
510483
) {
511484
const oldState = instance.state;
512485
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-
534486
startPhaseTimer(workInProgress, 'componentWillReceiveProps');
535487
instance.componentWillReceiveProps(newProps, newContext);
536488
stopPhaseTimer();
@@ -652,7 +604,14 @@ export default function(
652604

653605
if (__DEV__) {
654606
if (workInProgress.internalContextTag & StrictMode) {
655-
ReactStrictModeWarnings.recordLifecycleWarnings(
607+
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
608+
workInProgress,
609+
instance,
610+
);
611+
}
612+
613+
if (warnAboutDeprecatedLifecycles) {
614+
ReactStrictModeWarnings.recordDeprecationWarnings(
656615
workInProgress,
657616
instance,
658617
);
@@ -893,27 +852,6 @@ export default function(
893852
typeof instance.componentWillUpdate === 'function'
894853
) {
895854
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-
917855
startPhaseTimer(workInProgress, 'componentWillUpdate');
918856
instance.componentWillUpdate(newProps, newState, newContext);
919857
stopPhaseTimer();

packages/react-reconciler/src/ReactFiberScheduler.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ import {
3535
ClassComponent,
3636
ContextProvider,
3737
} from 'shared/ReactTypeOfWork';
38-
import {enableUserTimingAPI} from 'shared/ReactFeatureFlags';
38+
import {
39+
enableUserTimingAPI,
40+
warnAboutDeprecatedLifecycles,
41+
} from 'shared/ReactFeatureFlags';
3942
import getComponentName from 'shared/getComponentName';
4043
import invariant from 'fbjs/lib/invariant';
4144
import warning from 'fbjs/lib/warning';
@@ -316,7 +319,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
316319

317320
function commitAllLifeCycles() {
318321
if (__DEV__) {
319-
ReactStrictModeWarnings.flushPendingAsyncWarnings();
322+
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
323+
324+
if (warnAboutDeprecatedLifecycles) {
325+
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
326+
}
320327
}
321328

322329
while (nextEffect !== null) {

packages/react-reconciler/src/ReactStrictModeWarnings.js

+119-14
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ type FiberToLifecycleMap = Map<Fiber, LifecycleToComponentsMap>;
2323

2424
const ReactStrictModeWarnings = {
2525
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 {},
2830
};
2931

3032
if (__DEV__) {
@@ -34,17 +36,24 @@ if (__DEV__) {
3436
UNSAFE_componentWillUpdate: 'componentDidUpdate',
3537
};
3638

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();
3843

3944
// Tracks components we have already warned about.
40-
const didWarnSet = new Set();
45+
const didWarnAboutDeprecatedLifecycles = new Set();
46+
const didWarnAboutUnsafeLifecycles = new Set();
4147

4248
ReactStrictModeWarnings.discardPendingWarnings = () => {
43-
pendingWarningsMap = new Map();
49+
pendingComponentWillMountWarnings = [];
50+
pendingComponentWillReceivePropsWarnings = [];
51+
pendingComponentWillUpdateWarnings = [];
52+
pendingUnsafeLifecycleWarnings = new Map();
4453
};
4554

46-
ReactStrictModeWarnings.flushPendingAsyncWarnings = () => {
47-
((pendingWarningsMap: any): FiberToLifecycleMap).forEach(
55+
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
56+
((pendingUnsafeLifecycleWarnings: any): FiberToLifecycleMap).forEach(
4857
(lifecycleWarningsMap, strictRoot) => {
4958
const lifecyclesWarningMesages = [];
5059

@@ -54,7 +63,7 @@ if (__DEV__) {
5463
const componentNames = new Set();
5564
lifecycleWarnings.forEach(fiber => {
5665
componentNames.add(getComponentName(fiber) || 'Component');
57-
didWarnSet.add(fiber.type);
66+
didWarnAboutUnsafeLifecycles.add(fiber.type);
5867
});
5968

6069
const formatted = lifecycle.replace('UNSAFE_', '');
@@ -88,7 +97,7 @@ if (__DEV__) {
8897
},
8998
);
9099

91-
pendingWarningsMap = new Map();
100+
pendingUnsafeLifecycleWarnings = new Map();
92101
};
93102

94103
const getStrictRoot = (fiber: Fiber): Fiber => {
@@ -105,7 +114,103 @@ if (__DEV__) {
105114
return maybeStrictRoot;
106115
};
107116

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 = (
109214
fiber: Fiber,
110215
instance: any,
111216
) => {
@@ -116,21 +221,21 @@ if (__DEV__) {
116221
// are often vague and are likely to collide between 3rd party libraries.
117222
// An expand property is probably okay to use here since it's DEV-only,
118223
// and will only be set in the event of serious warnings.
119-
if (didWarnSet.has(fiber.type)) {
224+
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
120225
return;
121226
}
122227

123228
let warningsForRoot;
124-
if (!pendingWarningsMap.has(strictRoot)) {
229+
if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) {
125230
warningsForRoot = {
126231
UNSAFE_componentWillMount: [],
127232
UNSAFE_componentWillReceiveProps: [],
128233
UNSAFE_componentWillUpdate: [],
129234
};
130235

131-
pendingWarningsMap.set(strictRoot, warningsForRoot);
236+
pendingUnsafeLifecycleWarnings.set(strictRoot, warningsForRoot);
132237
} else {
133-
warningsForRoot = pendingWarningsMap.get(strictRoot);
238+
warningsForRoot = pendingUnsafeLifecycleWarnings.get(strictRoot);
134239
}
135240

136241
const unsafeLifecycles = [];

packages/react/src/__tests__/createReactClassIntegration-test.internal.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,18 @@ describe('create-react-class-integration', () => {
9595
'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
9696
'clean up subscriptions and pending requests in componentWillUnmount ' +
9797
'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',
100106
]);
101107

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

107111
ReactDOM.unmountComponentAtNode(container);
108112
instance.log('after unmount');

0 commit comments

Comments
 (0)