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