main
js 364 lines 13.5 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber} from './ReactInternalTypes';
11
12 import {runWithFiberInDEV} from './ReactCurrentFiber';
13 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
14 import {StrictLegacyMode} from './ReactTypeOfMode';
15
16 type FiberArray = Array<Fiber>;
17 type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
18
19 const ReactStrictModeWarnings = {
20 recordUnsafeLifecycleWarnings: (fiber: Fiber, instance: any): void => {},
21 flushPendingUnsafeLifecycleWarnings: (): void => {},
22 recordLegacyContextWarning: (fiber: Fiber, instance: any): void => {},
23 flushLegacyContextWarning: (): void => {},
24 discardPendingWarnings: (): void => {},
25 };
26
27 if (__DEV__) {
28 const findStrictRoot = (fiber: Fiber): Fiber | null => {
29 let maybeStrictRoot = null;
30
31 let node: null | Fiber = fiber;
32 while (node !== null) {
33 if (node.mode & StrictLegacyMode) {
34 maybeStrictRoot = node;
35 }
36 node = node.return;
37 }
38
39 return maybeStrictRoot;
40 };
41
42 const setToSortedString = (set: Set<string>) => {
43 const array = [];
44 set.forEach(value => {
45 array.push(value);
46 });
47 return array.sort().join(', ');
48 };
49
50 let pendingComponentWillMountWarnings: Array<Fiber> = [];
51 let pendingUNSAFE_ComponentWillMountWarnings: Array<Fiber> = [];
52 let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
53 let pendingUNSAFE_ComponentWillReceivePropsWarnings: Array<Fiber> = [];
54 let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
55 let pendingUNSAFE_ComponentWillUpdateWarnings: Array<Fiber> = [];
56
57 // Tracks components we have already warned about.
58 const didWarnAboutUnsafeLifecycles = new Set<mixed>();
59
60 ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = (
61 fiber: Fiber,
62 instance: any,
63 ) => {
64 // Dedupe strategy: Warn once per component.
65 if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
66 return;
67 }
68
69 if (
70 typeof instance.componentWillMount === 'function' &&
71 // Don't warn about react-lifecycles-compat polyfilled components.
72 instance.componentWillMount.__suppressDeprecationWarning !== true
73 ) {
74 pendingComponentWillMountWarnings.push(fiber);
75 }
76
77 if (
78 fiber.mode & StrictLegacyMode &&
79 typeof instance.UNSAFE_componentWillMount === 'function'
80 ) {
81 pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
82 }
83
84 if (
85 typeof instance.componentWillReceiveProps === 'function' &&
86 instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
87 ) {
88 pendingComponentWillReceivePropsWarnings.push(fiber);
89 }
90
91 if (
92 fiber.mode & StrictLegacyMode &&
93 typeof instance.UNSAFE_componentWillReceiveProps === 'function'
94 ) {
95 pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
96 }
97
98 if (
99 typeof instance.componentWillUpdate === 'function' &&
100 instance.componentWillUpdate.__suppressDeprecationWarning !== true
101 ) {
102 pendingComponentWillUpdateWarnings.push(fiber);
103 }
104
105 if (
106 fiber.mode & StrictLegacyMode &&
107 typeof instance.UNSAFE_componentWillUpdate === 'function'
108 ) {
109 pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
110 }
111 };
112
113 ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
114 // We do an initial pass to gather component names
115 const componentWillMountUniqueNames = new Set<string>();
116 if (pendingComponentWillMountWarnings.length > 0) {
117 pendingComponentWillMountWarnings.forEach(fiber => {
118 componentWillMountUniqueNames.add(
119 getComponentNameFromFiber(fiber) || 'Component',
120 );
121 didWarnAboutUnsafeLifecycles.add(fiber.type);
122 });
123 pendingComponentWillMountWarnings = [];
124 }
125
126 const UNSAFE_componentWillMountUniqueNames = new Set<string>();
127 if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) {
128 pendingUNSAFE_ComponentWillMountWarnings.forEach(fiber => {
129 UNSAFE_componentWillMountUniqueNames.add(
130 getComponentNameFromFiber(fiber) || 'Component',
131 );
132 didWarnAboutUnsafeLifecycles.add(fiber.type);
133 });
134 pendingUNSAFE_ComponentWillMountWarnings = [];
135 }
136
137 const componentWillReceivePropsUniqueNames = new Set<string>();
138 if (pendingComponentWillReceivePropsWarnings.length > 0) {
139 pendingComponentWillReceivePropsWarnings.forEach(fiber => {
140 componentWillReceivePropsUniqueNames.add(
141 getComponentNameFromFiber(fiber) || 'Component',
142 );
143 didWarnAboutUnsafeLifecycles.add(fiber.type);
144 });
145
146 pendingComponentWillReceivePropsWarnings = [];
147 }
148
149 const UNSAFE_componentWillReceivePropsUniqueNames = new Set<string>();
150 if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) {
151 pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(fiber => {
152 UNSAFE_componentWillReceivePropsUniqueNames.add(
153 getComponentNameFromFiber(fiber) || 'Component',
154 );
155 didWarnAboutUnsafeLifecycles.add(fiber.type);
156 });
157
158 pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
159 }
160
161 const componentWillUpdateUniqueNames = new Set<string>();
162 if (pendingComponentWillUpdateWarnings.length > 0) {
163 pendingComponentWillUpdateWarnings.forEach(fiber => {
164 componentWillUpdateUniqueNames.add(
165 getComponentNameFromFiber(fiber) || 'Component',
166 );
167 didWarnAboutUnsafeLifecycles.add(fiber.type);
168 });
169
170 pendingComponentWillUpdateWarnings = [];
171 }
172
173 const UNSAFE_componentWillUpdateUniqueNames = new Set<string>();
174 if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) {
175 pendingUNSAFE_ComponentWillUpdateWarnings.forEach(fiber => {
176 UNSAFE_componentWillUpdateUniqueNames.add(
177 getComponentNameFromFiber(fiber) || 'Component',
178 );
179 didWarnAboutUnsafeLifecycles.add(fiber.type);
180 });
181
182 pendingUNSAFE_ComponentWillUpdateWarnings = [];
183 }
184
185 // Finally, we flush all the warnings
186 // UNSAFE_ ones before the deprecated ones, since they'll be 'louder'
187 if (UNSAFE_componentWillMountUniqueNames.size > 0) {
188 const sortedNames = setToSortedString(
189 UNSAFE_componentWillMountUniqueNames,
190 );
191 console.error(
192 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
193 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
194 '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
195 '\nPlease update the following components: %s',
196 sortedNames,
197 );
198 }
199
200 if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) {
201 const sortedNames = setToSortedString(
202 UNSAFE_componentWillReceivePropsUniqueNames,
203 );
204 console.error(
205 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
206 'and may indicate bugs in your code. ' +
207 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
208 '* Move data fetching code or side effects to componentDidUpdate.\n' +
209 "* If you're updating state whenever props change, " +
210 'refactor your code to use memoization techniques or move it to ' +
211 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n' +
212 '\nPlease update the following components: %s',
213 sortedNames,
214 );
215 }
216
217 if (UNSAFE_componentWillUpdateUniqueNames.size > 0) {
218 const sortedNames = setToSortedString(
219 UNSAFE_componentWillUpdateUniqueNames,
220 );
221 console.error(
222 'Using UNSAFE_componentWillUpdate in strict mode is not recommended ' +
223 'and may indicate bugs in your code. ' +
224 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
225 '* Move data fetching code or side effects to componentDidUpdate.\n' +
226 '\nPlease update the following components: %s',
227 sortedNames,
228 );
229 }
230
231 if (componentWillMountUniqueNames.size > 0) {
232 const sortedNames = setToSortedString(componentWillMountUniqueNames);
233
234 console.warn(
235 'componentWillMount has been renamed, and is not recommended for use. ' +
236 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
237 '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
238 '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
239 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
240 'To rename all deprecated lifecycles to their new names, you can run ' +
241 '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
242 '\nPlease update the following components: %s',
243 sortedNames,
244 );
245 }
246
247 if (componentWillReceivePropsUniqueNames.size > 0) {
248 const sortedNames = setToSortedString(
249 componentWillReceivePropsUniqueNames,
250 );
251
252 console.warn(
253 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
254 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
255 '* Move data fetching code or side effects to componentDidUpdate.\n' +
256 "* If you're updating state whenever props change, refactor your " +
257 'code to use memoization techniques or move it to ' +
258 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n' +
259 '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
260 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
261 'To rename all deprecated lifecycles to their new names, you can run ' +
262 '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
263 '\nPlease update the following components: %s',
264 sortedNames,
265 );
266 }
267
268 if (componentWillUpdateUniqueNames.size > 0) {
269 const sortedNames = setToSortedString(componentWillUpdateUniqueNames);
270
271 console.warn(
272 'componentWillUpdate has been renamed, and is not recommended for use. ' +
273 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
274 '* Move data fetching code or side effects to componentDidUpdate.\n' +
275 '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
276 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
277 'To rename all deprecated lifecycles to their new names, you can run ' +
278 '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
279 '\nPlease update the following components: %s',
280 sortedNames,
281 );
282 }
283 };
284
285 let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
286
287 // Tracks components we have already warned about.
288 const didWarnAboutLegacyContext = new Set<mixed>();
289
290 ReactStrictModeWarnings.recordLegacyContextWarning = (
291 fiber: Fiber,
292 instance: any,
293 ) => {
294 const strictRoot = findStrictRoot(fiber);
295 if (strictRoot === null) {
296 console.error(
297 'Expected to find a StrictMode component in a strict mode tree. ' +
298 'This error is likely caused by a bug in React. Please file an issue.',
299 );
300 return;
301 }
302
303 // Dedup strategy: Warn once per component.
304 if (didWarnAboutLegacyContext.has(fiber.type)) {
305 return;
306 }
307
308 let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
309
310 if (
311 fiber.type.contextTypes != null ||
312 fiber.type.childContextTypes != null ||
313 (instance !== null && typeof instance.getChildContext === 'function')
314 ) {
315 if (warningsForRoot === undefined) {
316 warningsForRoot = [];
317 pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
318 }
319 warningsForRoot.push(fiber);
320 }
321 };
322
323 ReactStrictModeWarnings.flushLegacyContextWarning = () => {
324 (pendingLegacyContextWarning as any as FiberToFiberComponentsMap).forEach(
325 (fiberArray: FiberArray, strictRoot) => {
326 if (fiberArray.length === 0) {
327 return;
328 }
329 const firstFiber = fiberArray[0];
330
331 const uniqueNames = new Set<string>();
332 fiberArray.forEach(fiber => {
333 uniqueNames.add(getComponentNameFromFiber(fiber) || 'Component');
334 didWarnAboutLegacyContext.add(fiber.type);
335 });
336
337 const sortedNames = setToSortedString(uniqueNames);
338
339 runWithFiberInDEV(firstFiber, () => {
340 console.error(
341 'Legacy context API has been detected within a strict-mode tree.' +
342 '\n\nThe old API will be supported in all 16.x releases, but applications ' +
343 'using it should migrate to the new version.' +
344 '\n\nPlease update the following components: %s' +
345 '\n\nLearn more about this warning here: https://react.dev/link/legacy-context',
346 sortedNames,
347 );
348 });
349 },
350 );
351 };
352
353 ReactStrictModeWarnings.discardPendingWarnings = () => {
354 pendingComponentWillMountWarnings = [];
355 pendingUNSAFE_ComponentWillMountWarnings = [];
356 pendingComponentWillReceivePropsWarnings = [];
357 pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
358 pendingComponentWillUpdateWarnings = [];
359 pendingUNSAFE_ComponentWillUpdateWarnings = [];
360 pendingLegacyContextWarning = new Map();
361 };
362 }
363
364 export default ReactStrictModeWarnings;