| 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 | |
| 8 | import getComponentNameFromType from 'shared/getComponentNameFromType'; |
| 9 | import ReactSharedInternals from 'shared/ReactSharedInternals'; |
| 10 | import hasOwnProperty from 'shared/hasOwnProperty'; |
| 11 | import assign from 'shared/assign'; |
| 12 | import { |
| 13 | REACT_ELEMENT_TYPE, |
| 14 | REACT_FRAGMENT_TYPE, |
| 15 | REACT_LAZY_TYPE, |
| 16 | REACT_OPTIMISTIC_KEY, |
| 17 | } from 'shared/ReactSymbols'; |
| 18 | import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; |
| 19 | import isArray from 'shared/isArray'; |
| 20 | import {ownerStackLimit, enableOptimisticKey} from 'shared/ReactFeatureFlags'; |
| 21 | |
| 22 | const createTask = |
| 23 | // eslint-disable-next-line react-internal/no-production-logging |
| 24 | __DEV__ && console.createTask |
| 25 | ? // eslint-disable-next-line react-internal/no-production-logging |
| 26 | console.createTask |
| 27 | : () => null; |
| 28 | |
| 29 | function getTaskName(type) { |
| 30 | if (type === REACT_FRAGMENT_TYPE) { |
| 31 | return '<>'; |
| 32 | } |
| 33 | if ( |
| 34 | typeof type === 'object' && |
| 35 | type !== null && |
| 36 | type.$$typeof === REACT_LAZY_TYPE |
| 37 | ) { |
| 38 | // We don't want to eagerly initialize the initializer in DEV mode so we can't |
| 39 | // call it to extract the type so we don't know the type of this component. |
| 40 | return '<...>'; |
| 41 | } |
| 42 | try { |
| 43 | const name = getComponentNameFromType(type); |
| 44 | return name ? '<' + name + '>' : '<...>'; |
| 45 | } catch (x) { |
| 46 | return '<...>'; |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | function getOwner() { |
| 51 | if (__DEV__) { |
| 52 | const dispatcher = ReactSharedInternals.A; |
| 53 | if (dispatcher === null) { |
| 54 | return null; |
| 55 | } |
| 56 | return dispatcher.getOwner(); |
| 57 | } |
| 58 | return null; |
| 59 | } |
| 60 | |
| 61 | // v8 (Chromium, Node.js) defaults to 10 |
| 62 | // SpiderMonkey (Firefox) does not support Error.stackTraceLimit |
| 63 | // JSC (Safari) defaults to 100 |
| 64 | // The lower the limit, the more likely we'll not reach react_stack_bottom_frame |
| 65 | // The higher the limit, the slower Error() is when not inspecting with a debugger. |
| 66 | // When inspecting with a debugger, Error.stackTraceLimit has no impact on Error() performance (in v8). |
| 67 | const ownerStackTraceLimit = 10; |
| 68 | |
| 69 | /** @noinline */ |
| 70 | function UnknownOwner() { |
| 71 | /** @noinline */ |
| 72 | return (() => Error('react-stack-top-frame'))(); |
| 73 | } |
| 74 | const createFakeCallStack = { |
| 75 | react_stack_bottom_frame: function (callStackForError) { |
| 76 | return callStackForError(); |
| 77 | }, |
| 78 | }; |
| 79 | |
| 80 | let specialPropKeyWarningShown; |
| 81 | let didWarnAboutElementRef; |
| 82 | let didWarnAboutOldJSXRuntime; |
| 83 | let unknownOwnerDebugStack; |
| 84 | let unknownOwnerDebugTask; |
| 85 | |
| 86 | if (__DEV__) { |
| 87 | didWarnAboutElementRef = {}; |
| 88 | |
| 89 | // We use this technique to trick minifiers to preserve the function name. |
| 90 | unknownOwnerDebugStack = createFakeCallStack.react_stack_bottom_frame.bind( |
| 91 | createFakeCallStack, |
| 92 | UnknownOwner, |
| 93 | )(); |
| 94 | unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner)); |
| 95 | } |
| 96 | |
| 97 | function hasValidRef(config) { |
| 98 | if (__DEV__) { |
| 99 | if (hasOwnProperty.call(config, 'ref')) { |
| 100 | const getter = Object.getOwnPropertyDescriptor(config, 'ref').get; |
| 101 | if (getter && getter.isReactWarning) { |
| 102 | return false; |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | return config.ref !== undefined; |
| 107 | } |
| 108 | |
| 109 | function hasValidKey(config) { |
| 110 | if (__DEV__) { |
| 111 | if (hasOwnProperty.call(config, 'key')) { |
| 112 | const getter = Object.getOwnPropertyDescriptor(config, 'key').get; |
| 113 | if (getter && getter.isReactWarning) { |
| 114 | return false; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | return config.key !== undefined; |
| 119 | } |
| 120 | |
| 121 | function defineKeyPropWarningGetter(props, displayName) { |
| 122 | if (__DEV__) { |
| 123 | const warnAboutAccessingKey = function () { |
| 124 | if (!specialPropKeyWarningShown) { |
| 125 | specialPropKeyWarningShown = true; |
| 126 | console.error( |
| 127 | '%s: `key` is not a prop. Trying to access it will result ' + |
| 128 | 'in `undefined` being returned. If you need to access the same ' + |
| 129 | 'value within the child component, you should pass it as a different ' + |
| 130 | 'prop. (https://react.dev/link/special-props)', |
| 131 | displayName, |
| 132 | ); |
| 133 | } |
| 134 | }; |
| 135 | warnAboutAccessingKey.isReactWarning = true; |
| 136 | Object.defineProperty(props, 'key', { |
| 137 | get: warnAboutAccessingKey, |
| 138 | configurable: true, |
| 139 | }); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | function elementRefGetterWithDeprecationWarning() { |
| 144 | if (__DEV__) { |
| 145 | const componentName = getComponentNameFromType(this.type); |
| 146 | if (!didWarnAboutElementRef[componentName]) { |
| 147 | didWarnAboutElementRef[componentName] = true; |
| 148 | console.error( |
| 149 | 'Accessing element.ref was removed in React 19. ref is now a ' + |
| 150 | 'regular prop. It will be removed from the JSX Element ' + |
| 151 | 'type in a future release.', |
| 152 | ); |
| 153 | } |
| 154 | |
| 155 | // An undefined `element.ref` is coerced to `null` for |
| 156 | // backwards compatibility. |
| 157 | const refProp = this.props.ref; |
| 158 | return refProp !== undefined ? refProp : null; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Factory method to create a new React element. This no longer adheres to |
| 164 | * the class pattern, so do not use new to call it. Also, instanceof check |
| 165 | * will not work. Instead test $$typeof field against Symbol.for('react.transitional.element') to check |
| 166 | * if something is a React Element. |
| 167 | * |
| 168 | * @internal |
| 169 | */ |
| 170 | function ReactElement(type, key, props, owner, debugStack, debugTask) { |
| 171 | // Ignore whatever was passed as the ref argument and treat `props.ref` as |
| 172 | // the source of truth. The only thing we use this for is `element.ref`, |
| 173 | // which will log a deprecation warning on access. In the next release, we |
| 174 | // can remove `element.ref` as well as the `ref` argument. |
| 175 | const refProp = props.ref; |
| 176 | |
| 177 | // An undefined `element.ref` is coerced to `null` for |
| 178 | // backwards compatibility. |
| 179 | const ref = refProp !== undefined ? refProp : null; |
| 180 | |
| 181 | let element; |
| 182 | if (__DEV__) { |
| 183 | // In dev, make `ref` a non-enumerable property with a warning. It's non- |
| 184 | // enumerable so that test matchers and serializers don't access it and |
| 185 | // trigger the warning. |
| 186 | // |
| 187 | // `ref` will be removed from the element completely in a future release. |
| 188 | element = { |
| 189 | // This tag allows us to uniquely identify this as a React Element |
| 190 | $$typeof: REACT_ELEMENT_TYPE, |
| 191 | |
| 192 | // Built-in properties that belong on the element |
| 193 | type, |
| 194 | key, |
| 195 | |
| 196 | props, |
| 197 | |
| 198 | // Record the component responsible for creating this element. |
| 199 | _owner: owner, |
| 200 | }; |
| 201 | if (ref !== null) { |
| 202 | Object.defineProperty(element, 'ref', { |
| 203 | enumerable: false, |
| 204 | get: elementRefGetterWithDeprecationWarning, |
| 205 | }); |
| 206 | } else { |
| 207 | // Don't warn on access if a ref is not given. This reduces false |
| 208 | // positives in cases where a test serializer uses |
| 209 | // getOwnPropertyDescriptors to compare objects, like Jest does, which is |
| 210 | // a problem because it bypasses non-enumerability. |
| 211 | // |
| 212 | // So unfortunately this will trigger a false positive warning in Jest |
| 213 | // when the diff is printed: |
| 214 | // |
| 215 | // expect(<div ref={ref} />).toEqual(<span ref={ref} />); |
| 216 | // |
| 217 | // A bit sketchy, but this is what we've done for the `props.key` and |
| 218 | // `props.ref` accessors for years, which implies it will be good enough |
| 219 | // for `element.ref`, too. Let's see if anyone complains. |
| 220 | Object.defineProperty(element, 'ref', { |
| 221 | enumerable: false, |
| 222 | value: null, |
| 223 | }); |
| 224 | } |
| 225 | } else { |
| 226 | // In prod, `ref` is a regular property and _owner doesn't exist. |
| 227 | element = { |
| 228 | // This tag allows us to uniquely identify this as a React Element |
| 229 | $$typeof: REACT_ELEMENT_TYPE, |
| 230 | |
| 231 | // Built-in properties that belong on the element |
| 232 | type, |
| 233 | key, |
| 234 | ref, |
| 235 | |
| 236 | props, |
| 237 | }; |
| 238 | } |
| 239 | |
| 240 | if (__DEV__) { |
| 241 | // The validation flag is currently mutative. We put it on |
| 242 | // an external backing store so that we can freeze the whole object. |
| 243 | // This can be replaced with a WeakMap once they are implemented in |
| 244 | // commonly used development environments. |
| 245 | element._store = {}; |
| 246 | |
| 247 | // To make comparing ReactElements easier for testing purposes, we make |
| 248 | // the validation flag non-enumerable (where possible, which should |
| 249 | // include every environment we run tests in), so the test framework |
| 250 | // ignores it. |
| 251 | Object.defineProperty(element._store, 'validated', { |
| 252 | configurable: false, |
| 253 | enumerable: false, |
| 254 | writable: true, |
| 255 | value: 0, |
| 256 | }); |
| 257 | // debugInfo contains Server Component debug information. |
| 258 | Object.defineProperty(element, '_debugInfo', { |
| 259 | configurable: false, |
| 260 | enumerable: false, |
| 261 | writable: true, |
| 262 | value: null, |
| 263 | }); |
| 264 | Object.defineProperty(element, '_debugStack', { |
| 265 | configurable: false, |
| 266 | enumerable: false, |
| 267 | writable: true, |
| 268 | value: debugStack, |
| 269 | }); |
| 270 | Object.defineProperty(element, '_debugTask', { |
| 271 | configurable: false, |
| 272 | enumerable: false, |
| 273 | writable: true, |
| 274 | value: debugTask, |
| 275 | }); |
| 276 | if (Object.freeze) { |
| 277 | Object.freeze(element.props); |
| 278 | Object.freeze(element); |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | return element; |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * https://github.com/reactjs/rfcs/pull/107 |
| 287 | * @param {*} type |
| 288 | * @param {object} props |
| 289 | * @param {string} key |
| 290 | */ |
| 291 | export function jsxProd(type, config, maybeKey) { |
| 292 | let key = null; |
| 293 | |
| 294 | // Currently, key can be spread in as a prop. This causes a potential |
| 295 | // issue if key is also explicitly declared (ie. <div {...props} key="Hi" /> |
| 296 | // or <div key="Hi" {...props} /> ). We want to deprecate key spread, |
| 297 | // but as an intermediary step, we will use jsxDEV for everything except |
| 298 | // <div {...props} key="Hi" />, because we aren't currently able to tell if |
| 299 | // key is explicitly declared to be undefined or not. |
| 300 | if (maybeKey !== undefined) { |
| 301 | if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) { |
| 302 | key = REACT_OPTIMISTIC_KEY; |
| 303 | } else { |
| 304 | if (__DEV__) { |
| 305 | checkKeyStringCoercion(maybeKey); |
| 306 | } |
| 307 | key = '' + maybeKey; |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | if (hasValidKey(config)) { |
| 312 | if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) { |
| 313 | key = REACT_OPTIMISTIC_KEY; |
| 314 | } else { |
| 315 | if (__DEV__) { |
| 316 | checkKeyStringCoercion(config.key); |
| 317 | } |
| 318 | key = '' + config.key; |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | let props; |
| 323 | if (!('key' in config)) { |
| 324 | // If key was not spread in, we can reuse the original props object. This |
| 325 | // only works for `jsx`, not `createElement`, because `jsx` is a compiler |
| 326 | // target and the compiler always passes a new object. For `createElement`, |
| 327 | // we can't assume a new object is passed every time because it can be |
| 328 | // called manually. |
| 329 | // |
| 330 | // Spreading key is a warning in dev. In a future release, we will not |
| 331 | // remove a spread key from the props object. (But we'll still warn.) We'll |
| 332 | // always pass the object straight through. |
| 333 | props = config; |
| 334 | } else { |
| 335 | // We need to remove reserved props (key, prop, ref). Create a fresh props |
| 336 | // object and copy over all the non-reserved props. We don't use `delete` |
| 337 | // because in V8 it will deopt the object to dictionary mode. |
| 338 | props = {}; |
| 339 | for (const propName in config) { |
| 340 | // Skip over reserved prop names |
| 341 | if (propName !== 'key') { |
| 342 | props[propName] = config[propName]; |
| 343 | } |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | return ReactElement(type, key, props, getOwner(), undefined, undefined); |
| 348 | } |
| 349 | |
| 350 | // While `jsxDEV` should never be called when running in production, we do |
| 351 | // support `jsx` and `jsxs` when running in development. This supports the case |
| 352 | // where a third-party dependency ships code that was compiled for production; |
| 353 | // we want to still provide warnings in development. |
| 354 | // |
| 355 | // So these functions are the _dev_ implementations of the _production_ |
| 356 | // API signatures. |
| 357 | // |
| 358 | // Since these functions are dev-only, it's ok to add an indirection here. They |
| 359 | // only exist to provide different versions of `isStaticChildren`. (We shouldn't |
| 360 | // use this pattern for the prod versions, though, because it will add an call |
| 361 | // frame.) |
| 362 | export function jsxProdSignatureRunningInDevWithDynamicChildren( |
| 363 | type, |
| 364 | config, |
| 365 | maybeKey, |
| 366 | ) { |
| 367 | if (__DEV__) { |
| 368 | const isStaticChildren = false; |
| 369 | const trackActualOwner = |
| 370 | __DEV__ && |
| 371 | ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit; |
| 372 | let debugStackDEV = false; |
| 373 | if (__DEV__) { |
| 374 | if (trackActualOwner) { |
| 375 | const previousStackTraceLimit = Error.stackTraceLimit; |
| 376 | Error.stackTraceLimit = ownerStackTraceLimit; |
| 377 | debugStackDEV = Error('react-stack-top-frame'); |
| 378 | Error.stackTraceLimit = previousStackTraceLimit; |
| 379 | } else { |
| 380 | debugStackDEV = unknownOwnerDebugStack; |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | return jsxDEVImpl( |
| 385 | type, |
| 386 | config, |
| 387 | maybeKey, |
| 388 | isStaticChildren, |
| 389 | debugStackDEV, |
| 390 | __DEV__ && |
| 391 | (trackActualOwner |
| 392 | ? createTask(getTaskName(type)) |
| 393 | : unknownOwnerDebugTask), |
| 394 | ); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | export function jsxProdSignatureRunningInDevWithStaticChildren( |
| 399 | type, |
| 400 | config, |
| 401 | maybeKey, |
| 402 | ) { |
| 403 | if (__DEV__) { |
| 404 | const isStaticChildren = true; |
| 405 | const trackActualOwner = |
| 406 | __DEV__ && |
| 407 | ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit; |
| 408 | let debugStackDEV = false; |
| 409 | if (__DEV__) { |
| 410 | if (trackActualOwner) { |
| 411 | const previousStackTraceLimit = Error.stackTraceLimit; |
| 412 | Error.stackTraceLimit = ownerStackTraceLimit; |
| 413 | debugStackDEV = Error('react-stack-top-frame'); |
| 414 | Error.stackTraceLimit = previousStackTraceLimit; |
| 415 | } else { |
| 416 | debugStackDEV = unknownOwnerDebugStack; |
| 417 | } |
| 418 | } |
| 419 | return jsxDEVImpl( |
| 420 | type, |
| 421 | config, |
| 422 | maybeKey, |
| 423 | isStaticChildren, |
| 424 | debugStackDEV, |
| 425 | __DEV__ && |
| 426 | (trackActualOwner |
| 427 | ? createTask(getTaskName(type)) |
| 428 | : unknownOwnerDebugTask), |
| 429 | ); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | const didWarnAboutKeySpread = {}; |
| 434 | |
| 435 | /** |
| 436 | * https://github.com/reactjs/rfcs/pull/107 |
| 437 | * @param {*} type |
| 438 | * @param {object} props |
| 439 | * @param {string} key |
| 440 | */ |
| 441 | export function jsxDEV(type, config, maybeKey, isStaticChildren) { |
| 442 | const trackActualOwner = |
| 443 | __DEV__ && |
| 444 | ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit; |
| 445 | let debugStackDEV = false; |
| 446 | if (__DEV__) { |
| 447 | if (trackActualOwner) { |
| 448 | const previousStackTraceLimit = Error.stackTraceLimit; |
| 449 | Error.stackTraceLimit = ownerStackTraceLimit; |
| 450 | debugStackDEV = Error('react-stack-top-frame'); |
| 451 | Error.stackTraceLimit = previousStackTraceLimit; |
| 452 | } else { |
| 453 | debugStackDEV = unknownOwnerDebugStack; |
| 454 | } |
| 455 | } |
| 456 | return jsxDEVImpl( |
| 457 | type, |
| 458 | config, |
| 459 | maybeKey, |
| 460 | isStaticChildren, |
| 461 | debugStackDEV, |
| 462 | __DEV__ && |
| 463 | (trackActualOwner |
| 464 | ? createTask(getTaskName(type)) |
| 465 | : unknownOwnerDebugTask), |
| 466 | ); |
| 467 | } |
| 468 | |
| 469 | function jsxDEVImpl( |
| 470 | type, |
| 471 | config, |
| 472 | maybeKey, |
| 473 | isStaticChildren, |
| 474 | debugStack, |
| 475 | debugTask, |
| 476 | ) { |
| 477 | if (__DEV__) { |
| 478 | // We don't warn for invalid element type here because with owner stacks, |
| 479 | // we error in the renderer. The renderer is the only one that knows what |
| 480 | // types are valid for this particular renderer so we let it error there. |
| 481 | |
| 482 | // Skip key warning if the type isn't valid since our key validation logic |
| 483 | // doesn't expect a non-string/function type and can throw confusing |
| 484 | // errors. We don't want exception behavior to differ between dev and |
| 485 | // prod. (Rendering will throw with a helpful message and as soon as the |
| 486 | // type is fixed, the key warnings will appear.) |
| 487 | // With owner stacks, we no longer need the type here so this comment is |
| 488 | // no longer true. Which is why we can run this even for invalid types. |
| 489 | const children = config.children; |
| 490 | if (children !== undefined) { |
| 491 | if (isStaticChildren) { |
| 492 | if (isArray(children)) { |
| 493 | for (let i = 0; i < children.length; i++) { |
| 494 | validateChildKeys(children[i]); |
| 495 | } |
| 496 | |
| 497 | if (Object.freeze) { |
| 498 | Object.freeze(children); |
| 499 | } |
| 500 | } else { |
| 501 | console.error( |
| 502 | 'React.jsx: Static children should always be an array. ' + |
| 503 | 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' + |
| 504 | 'Use the Babel transform instead.', |
| 505 | ); |
| 506 | } |
| 507 | } else { |
| 508 | validateChildKeys(children); |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | // Warn about key spread regardless of whether the type is valid. |
| 513 | if (hasOwnProperty.call(config, 'key')) { |
| 514 | const componentName = getComponentNameFromType(type); |
| 515 | const keys = Object.keys(config).filter(k => k !== 'key'); |
| 516 | const beforeExample = |
| 517 | keys.length > 0 |
| 518 | ? '{key: someKey, ' + keys.join(': ..., ') + ': ...}' |
| 519 | : '{key: someKey}'; |
| 520 | if (!didWarnAboutKeySpread[componentName + beforeExample]) { |
| 521 | const afterExample = |
| 522 | keys.length > 0 ? '{' + keys.join(': ..., ') + ': ...}' : '{}'; |
| 523 | console.error( |
| 524 | 'A props object containing a "key" prop is being spread into JSX:\n' + |
| 525 | ' let props = %s;\n' + |
| 526 | ' <%s {...props} />\n' + |
| 527 | 'React keys must be passed directly to JSX without using spread:\n' + |
| 528 | ' let props = %s;\n' + |
| 529 | ' <%s key={someKey} {...props} />', |
| 530 | beforeExample, |
| 531 | componentName, |
| 532 | afterExample, |
| 533 | componentName, |
| 534 | ); |
| 535 | didWarnAboutKeySpread[componentName + beforeExample] = true; |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | let key = null; |
| 540 | |
| 541 | // Currently, key can be spread in as a prop. This causes a potential |
| 542 | // issue if key is also explicitly declared (ie. <div {...props} key="Hi" /> |
| 543 | // or <div key="Hi" {...props} /> ). We want to deprecate key spread, |
| 544 | // but as an intermediary step, we will use jsxDEV for everything except |
| 545 | // <div {...props} key="Hi" />, because we aren't currently able to tell if |
| 546 | // key is explicitly declared to be undefined or not. |
| 547 | if (maybeKey !== undefined) { |
| 548 | if (enableOptimisticKey && maybeKey === REACT_OPTIMISTIC_KEY) { |
| 549 | key = REACT_OPTIMISTIC_KEY; |
| 550 | } else { |
| 551 | if (__DEV__) { |
| 552 | checkKeyStringCoercion(maybeKey); |
| 553 | } |
| 554 | key = '' + maybeKey; |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | if (hasValidKey(config)) { |
| 559 | if (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) { |
| 560 | key = REACT_OPTIMISTIC_KEY; |
| 561 | } else { |
| 562 | if (__DEV__) { |
| 563 | checkKeyStringCoercion(config.key); |
| 564 | } |
| 565 | key = '' + config.key; |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | let props; |
| 570 | if (!('key' in config)) { |
| 571 | // If key was not spread in, we can reuse the original props object. This |
| 572 | // only works for `jsx`, not `createElement`, because `jsx` is a compiler |
| 573 | // target and the compiler always passes a new object. For `createElement`, |
| 574 | // we can't assume a new object is passed every time because it can be |
| 575 | // called manually. |
| 576 | // |
| 577 | // Spreading key is a warning in dev. In a future release, we will not |
| 578 | // remove a spread key from the props object. (But we'll still warn.) We'll |
| 579 | // always pass the object straight through. |
| 580 | props = config; |
| 581 | } else { |
| 582 | // We need to remove reserved props (key, prop, ref). Create a fresh props |
| 583 | // object and copy over all the non-reserved props. We don't use `delete` |
| 584 | // because in V8 it will deopt the object to dictionary mode. |
| 585 | props = {}; |
| 586 | for (const propName in config) { |
| 587 | // Skip over reserved prop names |
| 588 | if (propName !== 'key') { |
| 589 | props[propName] = config[propName]; |
| 590 | } |
| 591 | } |
| 592 | } |
| 593 | |
| 594 | if (key) { |
| 595 | const displayName = |
| 596 | typeof type === 'function' |
| 597 | ? type.displayName || type.name || 'Unknown' |
| 598 | : type; |
| 599 | defineKeyPropWarningGetter(props, displayName); |
| 600 | } |
| 601 | |
| 602 | return ReactElement(type, key, props, getOwner(), debugStack, debugTask); |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | /** |
| 607 | * Create and return a new ReactElement of the given type. |
| 608 | * See https://reactjs.org/docs/react-api.html#createelement |
| 609 | */ |
| 610 | export function createElement(type, config, children) { |
| 611 | if (__DEV__) { |
| 612 | // We don't warn for invalid element type here because with owner stacks, |
| 613 | // we error in the renderer. The renderer is the only one that knows what |
| 614 | // types are valid for this particular renderer so we let it error there. |
| 615 | |
| 616 | // Skip key warning if the type isn't valid since our key validation logic |
| 617 | // doesn't expect a non-string/function type and can throw confusing |
| 618 | // errors. We don't want exception behavior to differ between dev and |
| 619 | // prod. (Rendering will throw with a helpful message and as soon as the |
| 620 | // type is fixed, the key warnings will appear.) |
| 621 | for (let i = 2; i < arguments.length; i++) { |
| 622 | validateChildKeys(arguments[i]); |
| 623 | } |
| 624 | |
| 625 | // Unlike the jsx() runtime, createElement() doesn't warn about key spread. |
| 626 | } |
| 627 | |
| 628 | let propName; |
| 629 | |
| 630 | // Reserved names are extracted |
| 631 | const props = {}; |
| 632 | |
| 633 | let key = null; |
| 634 | |
| 635 | if (config != null) { |
| 636 | if (__DEV__) { |
| 637 | if ( |
| 638 | !didWarnAboutOldJSXRuntime && |
| 639 | '__self' in config && |
| 640 | // Do not assume this is the result of an oudated JSX transform if key |
| 641 | // is present, because the modern JSX transform sometimes outputs |
| 642 | // createElement to preserve precedence between a static key and a |
| 643 | // spread key. To avoid false positive warnings, we never warn if |
| 644 | // there's a key. |
| 645 | !('key' in config) |
| 646 | ) { |
| 647 | didWarnAboutOldJSXRuntime = true; |
| 648 | console.warn( |
| 649 | 'Your app (or one of its dependencies) is using an outdated JSX ' + |
| 650 | 'transform. Update to the modern JSX transform for ' + |
| 651 | 'faster performance: https://react.dev/link/new-jsx-transform', |
| 652 | ); |
| 653 | } |
| 654 | } |
| 655 | |
| 656 | if (hasValidKey(config)) { |
| 657 | if (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) { |
| 658 | key = REACT_OPTIMISTIC_KEY; |
| 659 | } else { |
| 660 | if (__DEV__) { |
| 661 | checkKeyStringCoercion(config.key); |
| 662 | } |
| 663 | key = '' + config.key; |
| 664 | } |
| 665 | } |
| 666 | |
| 667 | // Remaining properties are added to a new props object |
| 668 | for (propName in config) { |
| 669 | if ( |
| 670 | hasOwnProperty.call(config, propName) && |
| 671 | // Skip over reserved prop names |
| 672 | propName !== 'key' && |
| 673 | // Even though we don't use these anymore in the runtime, we don't want |
| 674 | // them to appear as props, so in createElement we filter them out. |
| 675 | // We don't have to do this in the jsx() runtime because the jsx() |
| 676 | // transform never passed these as props; it used separate arguments. |
| 677 | propName !== '__self' && |
| 678 | propName !== '__source' |
| 679 | ) { |
| 680 | props[propName] = config[propName]; |
| 681 | } |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | // Children can be more than one argument, and those are transferred onto |
| 686 | // the newly allocated props object. |
| 687 | const childrenLength = arguments.length - 2; |
| 688 | if (childrenLength === 1) { |
| 689 | props.children = children; |
| 690 | } else if (childrenLength > 1) { |
| 691 | const childArray = Array(childrenLength); |
| 692 | for (let i = 0; i < childrenLength; i++) { |
| 693 | childArray[i] = arguments[i + 2]; |
| 694 | } |
| 695 | if (__DEV__) { |
| 696 | if (Object.freeze) { |
| 697 | Object.freeze(childArray); |
| 698 | } |
| 699 | } |
| 700 | props.children = childArray; |
| 701 | } |
| 702 | |
| 703 | // Resolve default props |
| 704 | if (type && type.defaultProps) { |
| 705 | const defaultProps = type.defaultProps; |
| 706 | for (propName in defaultProps) { |
| 707 | if (props[propName] === undefined) { |
| 708 | props[propName] = defaultProps[propName]; |
| 709 | } |
| 710 | } |
| 711 | } |
| 712 | if (__DEV__) { |
| 713 | if (key) { |
| 714 | const displayName = |
| 715 | typeof type === 'function' |
| 716 | ? type.displayName || type.name || 'Unknown' |
| 717 | : type; |
| 718 | defineKeyPropWarningGetter(props, displayName); |
| 719 | } |
| 720 | } |
| 721 | const trackActualOwner = |
| 722 | __DEV__ && |
| 723 | ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit; |
| 724 | let debugStackDEV = false; |
| 725 | if (__DEV__) { |
| 726 | if (trackActualOwner) { |
| 727 | const previousStackTraceLimit = Error.stackTraceLimit; |
| 728 | Error.stackTraceLimit = ownerStackTraceLimit; |
| 729 | debugStackDEV = Error('react-stack-top-frame'); |
| 730 | Error.stackTraceLimit = previousStackTraceLimit; |
| 731 | } else { |
| 732 | debugStackDEV = unknownOwnerDebugStack; |
| 733 | } |
| 734 | } |
| 735 | return ReactElement( |
| 736 | type, |
| 737 | key, |
| 738 | props, |
| 739 | getOwner(), |
| 740 | debugStackDEV, |
| 741 | __DEV__ && |
| 742 | (trackActualOwner |
| 743 | ? createTask(getTaskName(type)) |
| 744 | : unknownOwnerDebugTask), |
| 745 | ); |
| 746 | } |
| 747 | |
| 748 | export function cloneAndReplaceKey(oldElement, newKey) { |
| 749 | const clonedElement = ReactElement( |
| 750 | oldElement.type, |
| 751 | newKey, |
| 752 | oldElement.props, |
| 753 | !__DEV__ ? undefined : oldElement._owner, |
| 754 | __DEV__ && oldElement._debugStack, |
| 755 | __DEV__ && oldElement._debugTask, |
| 756 | ); |
| 757 | if (__DEV__) { |
| 758 | // The cloned element should inherit the original element's key validation. |
| 759 | if (oldElement._store) { |
| 760 | clonedElement._store.validated = oldElement._store.validated; |
| 761 | } |
| 762 | } |
| 763 | return clonedElement; |
| 764 | } |
| 765 | |
| 766 | /** |
| 767 | * Clone and return a new ReactElement using element as the starting point. |
| 768 | * See https://reactjs.org/docs/react-api.html#cloneelement |
| 769 | */ |
| 770 | export function cloneElement(element, config, children) { |
| 771 | if (element === null || element === undefined) { |
| 772 | throw new Error( |
| 773 | `The argument must be a React element, but you passed ${element}.`, |
| 774 | ); |
| 775 | } |
| 776 | |
| 777 | let propName; |
| 778 | |
| 779 | // Original props are copied |
| 780 | const props = assign({}, element.props); |
| 781 | |
| 782 | // Reserved names are extracted |
| 783 | let key = element.key; |
| 784 | |
| 785 | // Owner will be preserved, unless ref is overridden |
| 786 | let owner = !__DEV__ ? undefined : element._owner; |
| 787 | |
| 788 | if (config != null) { |
| 789 | if (hasValidRef(config)) { |
| 790 | owner = __DEV__ ? getOwner() : undefined; |
| 791 | } |
| 792 | if (hasValidKey(config)) { |
| 793 | if (enableOptimisticKey && config.key === REACT_OPTIMISTIC_KEY) { |
| 794 | key = REACT_OPTIMISTIC_KEY; |
| 795 | } else { |
| 796 | if (__DEV__) { |
| 797 | checkKeyStringCoercion(config.key); |
| 798 | } |
| 799 | key = '' + config.key; |
| 800 | } |
| 801 | } |
| 802 | |
| 803 | // Remaining properties override existing props |
| 804 | for (propName in config) { |
| 805 | if ( |
| 806 | hasOwnProperty.call(config, propName) && |
| 807 | // Skip over reserved prop names |
| 808 | propName !== 'key' && |
| 809 | // ...and maybe these, too, though we currently rely on them for |
| 810 | // warnings and debug information in dev. Need to decide if we're OK |
| 811 | // with dropping them. In the jsx() runtime it's not an issue because |
| 812 | // the data gets passed as separate arguments instead of props, but |
| 813 | // it would be nice to stop relying on them entirely so we can drop |
| 814 | // them from the internal Fiber field. |
| 815 | propName !== '__self' && |
| 816 | propName !== '__source' && |
| 817 | // Undefined `ref` is ignored by cloneElement. We treat it the same as |
| 818 | // if the property were missing. This is mostly for |
| 819 | // backwards compatibility. |
| 820 | !(propName === 'ref' && config.ref === undefined) |
| 821 | ) { |
| 822 | props[propName] = config[propName]; |
| 823 | } |
| 824 | } |
| 825 | } |
| 826 | |
| 827 | // Children can be more than one argument, and those are transferred onto |
| 828 | // the newly allocated props object. |
| 829 | const childrenLength = arguments.length - 2; |
| 830 | if (childrenLength === 1) { |
| 831 | props.children = children; |
| 832 | } else if (childrenLength > 1) { |
| 833 | const childArray = Array(childrenLength); |
| 834 | for (let i = 0; i < childrenLength; i++) { |
| 835 | childArray[i] = arguments[i + 2]; |
| 836 | } |
| 837 | props.children = childArray; |
| 838 | } |
| 839 | |
| 840 | const clonedElement = ReactElement( |
| 841 | element.type, |
| 842 | key, |
| 843 | props, |
| 844 | owner, |
| 845 | __DEV__ && element._debugStack, |
| 846 | __DEV__ && element._debugTask, |
| 847 | ); |
| 848 | |
| 849 | for (let i = 2; i < arguments.length; i++) { |
| 850 | validateChildKeys(arguments[i]); |
| 851 | } |
| 852 | |
| 853 | return clonedElement; |
| 854 | } |
| 855 | |
| 856 | /** |
| 857 | * Ensure that every element either is passed in a static location, in an |
| 858 | * array with an explicit keys property defined, or in an object literal |
| 859 | * with valid key property. |
| 860 | * |
| 861 | * @internal |
| 862 | * @param {ReactNode} node Statically passed child of any type. |
| 863 | */ |
| 864 | function validateChildKeys(node) { |
| 865 | if (__DEV__) { |
| 866 | // Mark elements as being in a valid static child position so they |
| 867 | // don't need keys. |
| 868 | if (isValidElement(node)) { |
| 869 | if (node._store) { |
| 870 | node._store.validated = 1; |
| 871 | } |
| 872 | } else if (isLazyType(node)) { |
| 873 | if (node._payload.status === 'fulfilled') { |
| 874 | if (isValidElement(node._payload.value) && node._payload.value._store) { |
| 875 | node._payload.value._store.validated = 1; |
| 876 | } |
| 877 | } else if (node._store) { |
| 878 | node._store.validated = 1; |
| 879 | } |
| 880 | } |
| 881 | } |
| 882 | } |
| 883 | |
| 884 | /** |
| 885 | * Verifies the object is a ReactElement. |
| 886 | * See https://reactjs.org/docs/react-api.html#isvalidelement |
| 887 | * @param {?object} object |
| 888 | * @return {boolean} True if `object` is a ReactElement. |
| 889 | * @final |
| 890 | */ |
| 891 | export function isValidElement(object) { |
| 892 | return ( |
| 893 | typeof object === 'object' && |
| 894 | object !== null && |
| 895 | object.$$typeof === REACT_ELEMENT_TYPE |
| 896 | ); |
| 897 | } |
| 898 | |
| 899 | export function isLazyType(object) { |
| 900 | return ( |
| 901 | typeof object === 'object' && |
| 902 | object !== null && |
| 903 | object.$$typeof === REACT_LAZY_TYPE |
| 904 | ); |
| 905 | } |