Use strict equality (===) to prevent type coercion bugs
if (debugInfo == null) {
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {ReactElement} from 'shared/ReactElementType';11import type {12 ReactPortal,13 Thenable,14 ReactContext,15 ReactDebugInfo,16 ReactComponentInfo,17 SuspenseListRevealOrder,18 ReactKey,19 ReactOptimisticKey,20} from 'shared/ReactTypes';21import type {Fiber} from './ReactInternalTypes';22import type {Lanes} from './ReactFiberLane';23import type {ThenableState} from './ReactFiberThenable';2425import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';26import {27 Placement,28 ChildDeletion,29 Forked,30 PlacementDEV,31} from './ReactFiberFlags';32import {NoMode, ConcurrentMode} from './ReactTypeOfMode';33import {34 getIteratorFn,35 ASYNC_ITERATOR,36 REACT_ELEMENT_TYPE,37 REACT_FRAGMENT_TYPE,38 REACT_PORTAL_TYPE,39 REACT_LAZY_TYPE,40 REACT_CONTEXT_TYPE,41 REACT_LEGACY_ELEMENT_TYPE,42 REACT_OPTIMISTIC_KEY,43} from 'shared/ReactSymbols';44import {45 HostRoot,46 HostText,47 HostPortal,48 Fragment,49 FunctionComponent,50} from './ReactWorkTags';51import isArray from 'shared/isArray';52import {53 enableAsyncIterableChildren,54 disableLegacyMode,55 enableFragmentRefs,56 enableOptimisticKey,57} from 'shared/ReactFeatureFlags';5859import {60 createWorkInProgress,61 resetWorkInProgress,62 createFiberFromElement,63 createFiberFromFragment,64 createFiberFromText,65 createFiberFromPortal,66 createFiberFromThrow,67} from './ReactFiber';68import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';69import {getIsHydrating} from './ReactFiberHydrationContext';70import {pushTreeFork} from './ReactFiberTreeContext';71import {72 SuspenseException,73 SuspenseActionException,74 createThenableState,75 trackUsedThenable,76 resolveLazy,77} from './ReactFiberThenable';78import {readContextDuringReconciliation} from './ReactFiberNewContext';7980import {runWithFiberInDEV} from './ReactCurrentFiber';8182// This tracks the thenables that are unwrapped during reconcilation.83let thenableState: ThenableState | null = null;84let thenableIndexCounter: number = 0;8586// Server Components Meta Data87let currentDebugInfo: null | ReactDebugInfo = null;8889function pushDebugInfo(90 debugInfo: null | ReactDebugInfo,91): null | ReactDebugInfo {92 if (!__DEV__) {93 return null;94 }95 const previousDebugInfo = currentDebugInfo;96 if (debugInfo == null) {97 // Leave inplace98 } else if (previousDebugInfo === null) {99 currentDebugInfo = debugInfo;100 } else {101 // If we have two debugInfo, we need to create a new one. This makes the array no longer102 // live so we'll miss any future updates if we received more so ideally we should always103 // do this after both have fully resolved/unsuspended.104 currentDebugInfo = previousDebugInfo.concat(debugInfo);105 }106 return previousDebugInfo;107}108109function getCurrentDebugTask(): null | ConsoleTask {110 // Get the debug task of the parent Server Component if there is one.111 if (__DEV__) {112 const debugInfo = currentDebugInfo;113 if (debugInfo != null) {114 for (let i = debugInfo.length - 1; i >= 0; i--) {115 if (debugInfo[i].name != null) {116 const componentInfo: ReactComponentInfo = debugInfo[i];117 const debugTask: ?ConsoleTask = componentInfo.debugTask;118 if (debugTask != null) {119 return debugTask;120 }121 }122 }123 }124 }125 return null;126}127128let didWarnAboutMaps;129let didWarnAboutGenerators;130let ownerHasKeyUseWarning;131let ownerHasFunctionTypeWarning;132let ownerHasSymbolTypeWarning;133let warnForMissingKey = (134 returnFiber: Fiber,135 workInProgress: Fiber,136 child: mixed,137) => {};138139if (__DEV__) {140 didWarnAboutMaps = false;141 didWarnAboutGenerators = false;142143 /**144 * Warn if there's no key explicitly set on dynamic arrays of children or145 * object keys are not valid. This allows us to keep track of children between146 * updates.147 */148 ownerHasKeyUseWarning = {} as {[string]: boolean};149 ownerHasFunctionTypeWarning = {} as {[string]: boolean};150 ownerHasSymbolTypeWarning = {} as {[string]: boolean};151152 warnForMissingKey = (153 returnFiber: Fiber,154 workInProgress: Fiber,155 child: mixed,156 ) => {157 if (child === null || typeof child !== 'object') {158 return;159 }160 if (161 !child._store ||162 ((child._store.validated || child.key != null) &&163 child._store.validated !== 2)164 ) {165 return;166 }167168 if (typeof child._store !== 'object') {169 throw new Error(170 'React Component in warnForMissingKey should have a _store. ' +171 'This error is likely caused by a bug in React. Please file an issue.',172 );173 }174175 // $FlowFixMe[cannot-write] unable to narrow type from mixed to writable object176 child._store.validated = 1;177178 const componentName = getComponentNameFromFiber(returnFiber);179180 const componentKey = componentName || 'null';181 if (ownerHasKeyUseWarning[componentKey]) {182 return;183 }184 ownerHasKeyUseWarning[componentKey] = true;185186 const childOwner = child._owner;187 const parentOwner = returnFiber._debugOwner;188189 let currentComponentErrorInfo = '';190 if (parentOwner && typeof parentOwner.tag === 'number') {191 const name = getComponentNameFromFiber(parentOwner as any);192 if (name) {193 currentComponentErrorInfo =194 '\n\nCheck the render method of `' + name + '`.';195 }196 }197 if (!currentComponentErrorInfo) {198 if (componentName) {199 currentComponentErrorInfo = `\n\nCheck the top-level render call using <${componentName}>.`;200 }201 }202203 // Usually the current owner is the offender, but if it accepts children as a204 // property, it may be the creator of the child that's responsible for205 // assigning it a key.206 let childOwnerAppendix = '';207 if (childOwner != null && parentOwner !== childOwner) {208 let ownerName = null;209 if (typeof childOwner.tag === 'number') {210 ownerName = getComponentNameFromFiber(childOwner as any);211 } else if (typeof childOwner.name === 'string') {212 ownerName = childOwner.name;213 }214 if (ownerName) {215 // Give the component that originally created this child.216 childOwnerAppendix = ` It was passed a child from ${ownerName}.`;217 }218 }219220 runWithFiberInDEV(workInProgress, () => {221 console.error(222 'Each child in a list should have a unique "key" prop.' +223 '%s%s See https://react.dev/link/warning-keys for more information.',224 currentComponentErrorInfo,225 childOwnerAppendix,226 );227 });228 };229}230231// Given a fragment, validate that it can only be provided with fragment props232// We do this here instead of BeginWork because the Fragment fiber doesn't have233// the whole props object, only the children and is shared with arrays.234function validateFragmentProps(235 element: ReactElement,236 fiber: null | Fiber,237 returnFiber: Fiber,238) {239 if (__DEV__) {240 const keys = Object.keys(element.props);241 for (let i = 0; i < keys.length; i++) {242 const key = keys[i];243 if (244 key !== 'children' &&245 key !== 'key' &&246 (enableFragmentRefs ? key !== 'ref' : true)247 ) {248 if (fiber === null) {249 // For unkeyed root fragments without refs (enableFragmentRefs),250 // there's no Fiber. We create a fake one just for error stack handling.251 fiber = createFiberFromElement(element, returnFiber.mode, 0);252 if (__DEV__) {253 fiber._debugInfo = currentDebugInfo;254 }255 fiber.return = returnFiber;256 }257 runWithFiberInDEV(258 fiber,259 erroredKey => {260 if (enableFragmentRefs) {261 console.error(262 'Invalid prop `%s` supplied to `React.Fragment`. ' +263 'React.Fragment can only have `key`, `ref`, and `children` props.',264 erroredKey,265 );266 } else {267 console.error(268 'Invalid prop `%s` supplied to `React.Fragment`. ' +269 'React.Fragment can only have `key` and `children` props.',270 erroredKey,271 );272 }273 },274 key,275 );276 break;277 }278 }279 }280}281282function unwrapThenable<T>(thenable: Thenable<T>): T {283 const index = thenableIndexCounter;284 thenableIndexCounter += 1;285 if (thenableState === null) {286 thenableState = createThenableState();287 }288 return trackUsedThenable(thenableState, thenable, index, null);289}290291function coerceRef(workInProgress: Fiber, element: ReactElement): void {292 // TODO: This is a temporary, intermediate step. Now that enableRefAsProp is on,293 // we should resolve the `ref` prop during the begin phase of the component294 // it's attached to (HostComponent, ClassComponent, etc).295 const refProp = element.props.ref;296 // TODO: With enableRefAsProp now rolled out, we shouldn't use the `ref` field. We297 // should always read the ref from the prop.298 workInProgress.ref = refProp !== undefined ? refProp : null;299}300301function throwOnInvalidObjectTypeImpl(returnFiber: Fiber, newChild: Object) {302 if (newChild.$$typeof === REACT_LEGACY_ELEMENT_TYPE) {303 throw new Error(304 'A React Element from an older version of React was rendered. ' +305 'This is not supported. It can happen if:\n' +306 '- Multiple copies of the "react" package is used.\n' +307 '- A library pre-bundled an old copy of "react" or "react/jsx-runtime".\n' +308 '- A compiler tries to "inline" JSX instead of using the runtime.',309 );310 }311312 // $FlowFixMe[method-unbinding]313 const childString = Object.prototype.toString.call(newChild);314315 throw new Error(316 `Objects are not valid as a React child (found: ${317 childString === '[object Object]'318 ? 'object with keys {' + Object.keys(newChild).join(', ') + '}'319 : childString320 }). ` +321 'If you meant to render a collection of children, use an array ' +322 'instead.',323 );324}325326function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {327 const debugTask = getCurrentDebugTask();328 if (__DEV__ && debugTask !== null) {329 debugTask.run(330 throwOnInvalidObjectTypeImpl.bind(null, returnFiber, newChild),331 );332 } else {333 throwOnInvalidObjectTypeImpl(returnFiber, newChild);334 }335}336337function warnOnFunctionTypeImpl(returnFiber: Fiber, invalidChild: Function) {338 if (__DEV__) {339 const parentName = getComponentNameFromFiber(returnFiber) || 'Component';340341 if (ownerHasFunctionTypeWarning[parentName]) {342 return;343 }344 ownerHasFunctionTypeWarning[parentName] = true;345346 const name = invalidChild.displayName || invalidChild.name || 'Component';347348 if (returnFiber.tag === HostRoot) {349 console.error(350 'Functions are not valid as a React child. This may happen if ' +351 'you return %s instead of <%s /> from render. ' +352 'Or maybe you meant to call this function rather than return it.\n' +353 ' root.render(%s)',354 name,355 name,356 name,357 );358 } else {359 console.error(360 'Functions are not valid as a React child. This may happen if ' +361 'you return %s instead of <%s /> from render. ' +362 'Or maybe you meant to call this function rather than return it.\n' +363 ' <%s>{%s}</%s>',364 name,365 name,366 parentName,367 name,368 parentName,369 );370 }371 }372}373374function warnOnFunctionType(returnFiber: Fiber, invalidChild: Function) {375 const debugTask = getCurrentDebugTask();376 if (__DEV__ && debugTask !== null) {377 debugTask.run(warnOnFunctionTypeImpl.bind(null, returnFiber, invalidChild));378 } else {379 warnOnFunctionTypeImpl(returnFiber, invalidChild);380 }381}382383function warnOnSymbolTypeImpl(returnFiber: Fiber, invalidChild: symbol) {384 if (__DEV__) {385 const parentName = getComponentNameFromFiber(returnFiber) || 'Component';386387 if (ownerHasSymbolTypeWarning[parentName]) {388 return;389 }390 ownerHasSymbolTypeWarning[parentName] = true;391392 // eslint-disable-next-line react-internal/safe-string-coercion393 const name = String(invalidChild);394395 if (returnFiber.tag === HostRoot) {396 console.error(397 'Symbols are not valid as a React child.\n' + ' root.render(%s)',398 name,399 );400 } else {401 console.error(402 'Symbols are not valid as a React child.\n' + ' <%s>%s</%s>',403 parentName,404 name,405 parentName,406 );407 }408 }409}410411function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) {412 const debugTask = getCurrentDebugTask();413 if (__DEV__ && debugTask !== null) {414 debugTask.run(warnOnSymbolTypeImpl.bind(null, returnFiber, invalidChild));415 } else {416 warnOnSymbolTypeImpl(returnFiber, invalidChild);417 }418}419420type ChildReconciler = (421 returnFiber: Fiber,422 currentFirstChild: Fiber | null,423 newChild: any,424 lanes: Lanes,425) => Fiber | null;426427// This wrapper function exists because I expect to clone the code in each path428// to be able to optimize each path individually by branching early. This needs429// a compiler or we can do it manually. Helpers that don't need this branching430// live outside of this function.431function createChildReconciler(432 shouldTrackSideEffects: boolean,433): ChildReconciler {434 function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {435 if (!shouldTrackSideEffects) {436 // Noop.437 return;438 }439 const deletions = returnFiber.deletions;440 if (deletions === null) {441 returnFiber.deletions = [childToDelete];442 returnFiber.flags |= ChildDeletion;443 } else {444 deletions.push(childToDelete);445 }446 }447448 function deleteRemainingChildren(449 returnFiber: Fiber,450 currentFirstChild: Fiber | null,451 ): null {452 if (!shouldTrackSideEffects) {453 // Noop.454 return null;455 }456457 // TODO: For the shouldClone case, this could be micro-optimized a bit by458 // assuming that after the first child we've already added everything.459 let childToDelete = currentFirstChild;460 while (childToDelete !== null) {461 deleteChild(returnFiber, childToDelete);462 childToDelete = childToDelete.sibling;463 }464 return null;465 }466467 function mapRemainingChildren(468 currentFirstChild: Fiber,469 ): Map<string | number | ReactOptimisticKey, Fiber> {470 // Add the remaining children to a temporary map so that we can find them by471 // keys quickly. Implicit (null) keys get added to this set with their index472 // instead.473 const existingChildren: Map<474 | string475 | number476 // This type is only here for the case when enableOptimisticKey is disabled.477 // Remove it after it ships.478 | ReactOptimisticKey,479 Fiber,480 > = new Map();481482 let existingChild: null | Fiber = currentFirstChild;483 while (existingChild !== null) {484 if (existingChild.key === null) {485 existingChildren.set(existingChild.index, existingChild);486 } else if (487 enableOptimisticKey &&488 existingChild.key === REACT_OPTIMISTIC_KEY489 ) {490 // For optimistic keys, we store the negative index (minus one) to differentiate491 // them from the regular indices. We'll look this up regardless of what the new492 // key is, if there's no other match.493 existingChildren.set(-existingChild.index - 1, existingChild);494 } else {495 existingChildren.set(existingChild.key, existingChild);496 }497 existingChild = existingChild.sibling;498 }499 return existingChildren;500 }501502 function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {503 // We currently set sibling to null and index to 0 here because it is easy504 // to forget to do before returning it. E.g. for the single child case.505 const clone = createWorkInProgress(fiber, pendingProps);506 clone.index = 0;507 clone.sibling = null;508 return clone;509 }510511 function placeChild(512 newFiber: Fiber,513 lastPlacedIndex: number,514 newIndex: number,515 ): number {516 newFiber.index = newIndex;517 if (!shouldTrackSideEffects) {518 // During hydration, the useId algorithm needs to know which fibers are519 // part of a list of children (arrays, iterators).520 newFiber.flags |= Forked;521 return lastPlacedIndex;522 }523 const current = newFiber.alternate;524 if (current !== null) {525 const oldIndex = current.index;526 if (oldIndex < lastPlacedIndex) {527 // This is a move. The fiber already existed, so this is not a new528 // mount; don't set PlacementDEV, which would cause StrictMode to529 // re-run the effects in its subtree as if it had remounted.530 newFiber.flags |= Placement;531 return lastPlacedIndex;532 } else {533 // This item can stay in place.534 return oldIndex;535 }536 } else {537 // This is an insertion.538 newFiber.flags |= Placement | PlacementDEV;539 return lastPlacedIndex;540 }541 }542543 function placeSingleChild(newFiber: Fiber): Fiber {544 // This is simpler for the single child case. We only need to do a545 // placement for inserting new children.546 if (shouldTrackSideEffects && newFiber.alternate === null) {547 newFiber.flags |= Placement | PlacementDEV;548 }549 return newFiber;550 }551552 function updateTextNode(553 returnFiber: Fiber,554 current: Fiber | null,555 textContent: string,556 lanes: Lanes,557 ) {558 if (current === null || current.tag !== HostText) {559 // Insert560 const created = createFiberFromText(textContent, returnFiber.mode, lanes);561 created.return = returnFiber;562 if (__DEV__) {563 // We treat the parent as the owner for stack purposes.564 created._debugOwner = returnFiber;565 created._debugTask = returnFiber._debugTask;566 created._debugInfo = currentDebugInfo;567 }568 return created;569 } else {570 // Update571 const existing = useFiber(current, textContent);572 existing.return = returnFiber;573 if (__DEV__) {574 existing._debugInfo = currentDebugInfo;575 }576 return existing;577 }578 }579580 function updateElement(581 returnFiber: Fiber,582 current: Fiber | null,583 element: ReactElement,584 lanes: Lanes,585 ): Fiber {586 const elementType = element.type;587 if (elementType === REACT_FRAGMENT_TYPE) {588 const updated = updateFragment(589 returnFiber,590 current,591 element.props.children,592 lanes,593 element.key,594 );595 if (enableFragmentRefs) {596 coerceRef(updated, element);597 }598 validateFragmentProps(element, updated, returnFiber);599 return updated;600 }601 if (current !== null) {602 if (603 current.elementType === elementType ||604 // Keep this check inline so it only runs on the false path:605 (__DEV__606 ? isCompatibleFamilyForHotReloading(current, element)607 : false) ||608 // Lazy types should reconcile their resolved type.609 // We need to do this after the Hot Reloading check above,610 // because hot reloading has different semantics than prod because611 // it doesn't resuspend. So we can't let the call below suspend.612 (typeof elementType === 'object' &&613 elementType !== null &&614 elementType.$$typeof === REACT_LAZY_TYPE &&615 resolveLazy(elementType) === current.type)616 ) {617 // Move based on index618 const existing = useFiber(current, element.props);619 coerceRef(existing, element);620 existing.return = returnFiber;621 if (__DEV__) {622 existing._debugOwner = element._owner;623 existing._debugInfo = currentDebugInfo;624 }625 return existing;626 }627 }628 // Insert629 const created = createFiberFromElement(element, returnFiber.mode, lanes);630 coerceRef(created, element);631 created.return = returnFiber;632 if (__DEV__) {633 created._debugInfo = currentDebugInfo;634 }635 return created;636 }637638 function updatePortal(639 returnFiber: Fiber,640 current: Fiber | null,641 portal: ReactPortal,642 lanes: Lanes,643 ): Fiber {644 if (645 current === null ||646 current.tag !== HostPortal ||647 current.stateNode.containerInfo !== portal.containerInfo ||648 current.stateNode.implementation !== portal.implementation649 ) {650 // Insert651 const created = createFiberFromPortal(portal, returnFiber.mode, lanes);652 created.return = returnFiber;653 if (__DEV__) {654 created._debugInfo = currentDebugInfo;655 }656 return created;657 } else {658 // Update659 const existing = useFiber(current, portal.children || []);660 if (enableOptimisticKey) {661 // If the old key was optimistic we need to now save the real one.662 existing.key = portal.key;663 }664 existing.return = returnFiber;665 if (__DEV__) {666 existing._debugInfo = currentDebugInfo;667 }668 return existing;669 }670 }671672 function updateFragment(673 returnFiber: Fiber,674 current: Fiber | null,675 fragment: Iterable<React$Node>,676 lanes: Lanes,677 key: ReactKey,678 ): Fiber {679 if (current === null || current.tag !== Fragment) {680 // Insert681 const created = createFiberFromFragment(682 fragment,683 returnFiber.mode,684 lanes,685 key,686 );687 created.return = returnFiber;688 if (__DEV__) {689 // We treat the parent as the owner for stack purposes.690 created._debugOwner = returnFiber;691 created._debugTask = returnFiber._debugTask;692 created._debugInfo = currentDebugInfo;693 }694 return created;695 } else {696 // Update697 const existing = useFiber(current, fragment);698 if (enableOptimisticKey) {699 // If the old key was optimistic we need to now save the real one.700 existing.key = key;701 }702 existing.return = returnFiber;703 if (__DEV__) {704 existing._debugInfo = currentDebugInfo;705 }706 return existing;707 }708 }709710 function createChild(711 returnFiber: Fiber,712 newChild: any,713 lanes: Lanes,714 ): Fiber | null {715 if (716 (typeof newChild === 'string' && newChild !== '') ||717 typeof newChild === 'number' ||718 typeof newChild === 'bigint'719 ) {720 // Text nodes don't have keys. If the previous node is implicitly keyed721 // we can continue to replace it without aborting even if it is not a text722 // node.723 const created = createFiberFromText(724 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint725 '' + newChild,726 returnFiber.mode,727 lanes,728 );729 created.return = returnFiber;730 if (__DEV__) {731 // We treat the parent as the owner for stack purposes.732 created._debugOwner = returnFiber;733 created._debugTask = returnFiber._debugTask;734 created._debugInfo = currentDebugInfo;735 }736 return created;737 }738739 if (typeof newChild === 'object' && newChild !== null) {740 switch (newChild.$$typeof) {741 case REACT_ELEMENT_TYPE: {742 const created = createFiberFromElement(743 newChild,744 returnFiber.mode,745 lanes,746 );747 coerceRef(created, newChild);748 created.return = returnFiber;749 if (__DEV__) {750 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);751 created._debugInfo = currentDebugInfo;752 currentDebugInfo = prevDebugInfo;753 }754 return created;755 }756 case REACT_PORTAL_TYPE: {757 const created = createFiberFromPortal(758 newChild,759 returnFiber.mode,760 lanes,761 );762 created.return = returnFiber;763 if (__DEV__) {764 created._debugInfo = currentDebugInfo;765 }766 return created;767 }768 case REACT_LAZY_TYPE: {769 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);770 const resolvedChild = resolveLazy(newChild as any);771 const created = createChild(returnFiber, resolvedChild, lanes);772 currentDebugInfo = prevDebugInfo;773 return created;774 }775 }776777 if (778 isArray(newChild) ||779 getIteratorFn(newChild) ||780 (enableAsyncIterableChildren &&781 typeof newChild[ASYNC_ITERATOR] === 'function')782 ) {783 const created = createFiberFromFragment(784 newChild,785 returnFiber.mode,786 lanes,787 null,788 );789 created.return = returnFiber;790 if (__DEV__) {791 // We treat the parent as the owner for stack purposes.792 created._debugOwner = returnFiber;793 created._debugTask = returnFiber._debugTask;794 // Make sure to not push again when handling the Fragment child.795 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);796 created._debugInfo = currentDebugInfo;797 currentDebugInfo = prevDebugInfo;798 }799 return created;800 }801802 // Usable node types803 //804 // Unwrap the inner value and recursively call this function again.805 if (typeof newChild.then === 'function') {806 const thenable: Thenable<any> = newChild as any;807 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);808 const created = createChild(809 returnFiber,810 unwrapThenable(thenable),811 lanes,812 );813 currentDebugInfo = prevDebugInfo;814 return created;815 }816817 // $FlowFixMe[invalid-compare]818 if (newChild.$$typeof === REACT_CONTEXT_TYPE) {819 const context: ReactContext<mixed> = newChild as any;820 return createChild(821 returnFiber,822 readContextDuringReconciliation(returnFiber, context, lanes),823 lanes,824 );825 }826827 throwOnInvalidObjectType(returnFiber, newChild);828 }829830 if (__DEV__) {831 if (typeof newChild === 'function') {832 warnOnFunctionType(returnFiber, newChild);833 }834 if (typeof newChild === 'symbol') {835 warnOnSymbolType(returnFiber, newChild);836 }837 }838839 return null;840 }841842 function updateSlot(843 returnFiber: Fiber,844 oldFiber: Fiber | null,845 newChild: any,846 lanes: Lanes,847 ): Fiber | null {848 // Update the fiber if the keys match, otherwise return null.849 const key = oldFiber !== null ? oldFiber.key : null;850851 if (852 (typeof newChild === 'string' && newChild !== '') ||853 typeof newChild === 'number' ||854 typeof newChild === 'bigint'855 ) {856 // Text nodes don't have keys. If the previous node is implicitly keyed857 // we can continue to replace it without aborting even if it is not a text858 // node.859 if (key !== null) {860 return null;861 }862 return updateTextNode(863 returnFiber,864 oldFiber,865 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint866 '' + newChild,867 lanes,868 );869 }870871 if (typeof newChild === 'object' && newChild !== null) {872 switch (newChild.$$typeof) {873 case REACT_ELEMENT_TYPE: {874 if (875 // If the old child was an optimisticKey, then we'd normally consider that a match,876 // but instead, we'll bail to return null from the slot which will bail to slow path.877 // That's to ensure that if the new key has a match elsewhere in the list, then that878 // takes precedence over assuming the identity of an optimistic slot.879 newChild.key === key880 ) {881 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);882 const updated = updateElement(883 returnFiber,884 oldFiber,885 newChild,886 lanes,887 );888 currentDebugInfo = prevDebugInfo;889 return updated;890 } else {891 return null;892 }893 }894 case REACT_PORTAL_TYPE: {895 if (896 // If the old child was an optimisticKey, then we'd normally consider that a match,897 // but instead, we'll bail to return null from the slot which will bail to slow path.898 // That's to ensure that if the new key has a match elsewhere in the list, then that899 // takes precedence over assuming the identity of an optimistic slot.900 newChild.key === key901 ) {902 return updatePortal(returnFiber, oldFiber, newChild, lanes);903 } else {904 return null;905 }906 }907 case REACT_LAZY_TYPE: {908 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);909 const resolvedChild = resolveLazy(newChild as any);910 const updated = updateSlot(911 returnFiber,912 oldFiber,913 resolvedChild,914 lanes,915 );916 currentDebugInfo = prevDebugInfo;917 return updated;918 }919 }920921 if (922 isArray(newChild) ||923 getIteratorFn(newChild) ||924 (enableAsyncIterableChildren &&925 typeof newChild[ASYNC_ITERATOR] === 'function')926 ) {927 if (key !== null) {928 return null;929 }930931 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);932 const updated = updateFragment(933 returnFiber,934 oldFiber,935 newChild,936 lanes,937 null,938 );939 currentDebugInfo = prevDebugInfo;940 return updated;941 }942943 // Usable node types944 //945 // Unwrap the inner value and recursively call this function again.946 if (typeof newChild.then === 'function') {947 const thenable: Thenable<any> = newChild as any;948 const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);949 const updated = updateSlot(950 returnFiber,951 oldFiber,952 unwrapThenable(thenable),953 lanes,954 );955 currentDebugInfo = prevDebugInfo;956 return updated;957 }958959 // $FlowFixMe[invalid-compare]960 if (newChild.$$typeof === REACT_CONTEXT_TYPE) {961 const context: ReactContext<mixed> = newChild as any;962 return updateSlot(963 returnFiber,964 oldFiber,965 readContextDuringReconciliation(returnFiber, context, lanes),966 lanes,967 );968 }969970 throwOnInvalidObjectType(returnFiber, newChild);971 }972973 if (__DEV__) {974 if (typeof newChild === 'function') {975 warnOnFunctionType(returnFiber, newChild);976 }977 if (typeof newChild === 'symbol') {978 warnOnSymbolType(returnFiber, newChild);979 }980 }981982 return null;983 }984985 function updateFromMap(986 existingChildren: Map<string | number | ReactOptimisticKey, Fiber>,987 returnFiber: Fiber,988 newIdx: number,989 newChild: any,990 lanes: Lanes,991 ): Fiber | null {992 if (993 (typeof newChild === 'string' && newChild !== '') ||994 typeof newChild === 'number' ||995 typeof newChild === 'bigint'996 ) {997 // Text nodes don't have keys, so we neither have to check the old nor998 // new node for the key. If both are text nodes, they match.999 const matchedFiber = existingChildren.get(newIdx) || null;1000 return updateTextNode(1001 returnFiber,1002 matchedFiber,1003 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint1004 '' + newChild,1005 lanes,1006 );1007 }10081009 if (typeof newChild === 'object' && newChild !== null) {1010 switch (newChild.$$typeof) {1011 case REACT_ELEMENT_TYPE: {1012 const matchedFiber =1013 existingChildren.get(1014 newChild.key === null ? newIdx : newChild.key,1015 ) ||1016 (enableOptimisticKey &&1017 // If the existing child was an optimistic key, we may still match on the index.1018 existingChildren.get(-newIdx - 1)) ||1019 null;1020 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);1021 const updated = updateElement(1022 returnFiber,1023 matchedFiber,1024 newChild,1025 lanes,1026 );1027 currentDebugInfo = prevDebugInfo;1028 return updated;1029 }1030 case REACT_PORTAL_TYPE: {1031 const matchedFiber =1032 existingChildren.get(1033 newChild.key === null ? newIdx : newChild.key,1034 ) ||1035 (enableOptimisticKey &&1036 // If the existing child was an optimistic key, we may still match on the index.1037 existingChildren.get(-newIdx - 1)) ||1038 null;1039 return updatePortal(returnFiber, matchedFiber, newChild, lanes);1040 }1041 case REACT_LAZY_TYPE: {1042 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);1043 const resolvedChild = resolveLazy(newChild as any);1044 const updated = updateFromMap(1045 existingChildren,1046 returnFiber,1047 newIdx,1048 resolvedChild,1049 lanes,1050 );1051 currentDebugInfo = prevDebugInfo;1052 return updated;1053 }1054 }10551056 if (1057 isArray(newChild) ||1058 getIteratorFn(newChild) ||1059 (enableAsyncIterableChildren &&1060 typeof newChild[ASYNC_ITERATOR] === 'function')1061 ) {1062 const matchedFiber = existingChildren.get(newIdx) || null;1063 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);1064 const updated = updateFragment(1065 returnFiber,1066 matchedFiber,1067 newChild,1068 lanes,1069 null,1070 );1071 currentDebugInfo = prevDebugInfo;1072 return updated;1073 }10741075 // Usable node types1076 //1077 // Unwrap the inner value and recursively call this function again.1078 if (typeof newChild.then === 'function') {1079 const thenable: Thenable<any> = newChild as any;1080 const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);1081 const updated = updateFromMap(1082 existingChildren,1083 returnFiber,1084 newIdx,1085 unwrapThenable(thenable),1086 lanes,1087 );1088 currentDebugInfo = prevDebugInfo;1089 return updated;1090 }10911092 // $FlowFixMe[invalid-compare]1093 if (newChild.$$typeof === REACT_CONTEXT_TYPE) {1094 const context: ReactContext<mixed> = newChild as any;1095 return updateFromMap(1096 existingChildren,1097 returnFiber,1098 newIdx,1099 readContextDuringReconciliation(returnFiber, context, lanes),1100 lanes,1101 );1102 }11031104 throwOnInvalidObjectType(returnFiber, newChild);1105 }11061107 if (__DEV__) {1108 if (typeof newChild === 'function') {1109 warnOnFunctionType(returnFiber, newChild);1110 }1111 if (typeof newChild === 'symbol') {1112 warnOnSymbolType(returnFiber, newChild);1113 }1114 }11151116 return null;1117 }11181119 /**1120 * Warns if there is a duplicate or missing key1121 */1122 function warnOnInvalidKey(1123 returnFiber: Fiber,1124 workInProgress: Fiber,1125 child: mixed,1126 knownKeys: Set<string> | null,1127 ): Set<string> | null {1128 if (__DEV__) {1129 if (typeof child !== 'object' || child === null) {1130 return knownKeys;1131 }1132 switch (child.$$typeof) {1133 case REACT_ELEMENT_TYPE:1134 case REACT_PORTAL_TYPE:1135 warnForMissingKey(returnFiber, workInProgress, child);1136 const key = child.key;1137 if (typeof key !== 'string') {1138 break;1139 }1140 if (knownKeys === null) {1141 knownKeys = new Set();1142 knownKeys.add(key);1143 break;1144 }1145 if (!knownKeys.has(key)) {1146 knownKeys.add(key);1147 break;1148 }1149 runWithFiberInDEV(workInProgress, () => {1150 console.error(1151 'Encountered two children with the same key, `%s`. ' +1152 'Keys should be unique so that components maintain their identity ' +1153 'across updates. Non-unique keys may cause children to be ' +1154 'duplicated and/or omitted — the behavior is unsupported and ' +1155 'could change in a future version.',1156 key,1157 );1158 });1159 break;1160 case REACT_LAZY_TYPE: {1161 const resolvedChild = resolveLazy(child as any);1162 warnOnInvalidKey(1163 returnFiber,1164 workInProgress,1165 resolvedChild,1166 knownKeys,1167 );1168 break;1169 }1170 default:1171 break;1172 }1173 }1174 return knownKeys;1175 }11761177 function reconcileChildrenArray(1178 returnFiber: Fiber,1179 currentFirstChild: Fiber | null,1180 newChildren: Array<any>,1181 lanes: Lanes,1182 ): Fiber | null {1183 // This algorithm can't optimize by searching from both ends since we1184 // don't have backpointers on fibers. I'm trying to see how far we can get1185 // with that model. If it ends up not being worth the tradeoffs, we can1186 // add it later.11871188 // Even with a two ended optimization, we'd want to optimize for the case1189 // where there are few changes and brute force the comparison instead of1190 // going for the Map. It'd like to explore hitting that path first in1191 // forward-only mode and only go for the Map once we notice that we need1192 // lots of look ahead. This doesn't handle reversal as well as two ended1193 // search but that's unusual. Besides, for the two ended optimization to1194 // work on Iterables, we'd need to copy the whole set.11951196 // In this first iteration, we'll just live with hitting the bad case1197 // (adding everything to a Map) in for every insert/move.11981199 // If you change this code, also update reconcileChildrenIterator() which1200 // uses the same algorithm.12011202 let knownKeys: Set<string> | null = null;12031204 let resultingFirstChild: Fiber | null = null;1205 let previousNewFiber: Fiber | null = null;12061207 let oldFiber = currentFirstChild;1208 let lastPlacedIndex = 0;1209 let newIdx = 0;1210 let nextOldFiber = null;1211 for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {1212 if (oldFiber.index > newIdx) {1213 nextOldFiber = oldFiber;1214 oldFiber = null;1215 } else {1216 nextOldFiber = oldFiber.sibling;1217 }1218 const newFiber = updateSlot(1219 returnFiber,1220 oldFiber,1221 newChildren[newIdx],1222 lanes,1223 );1224 if (newFiber === null) {1225 // TODO: This breaks on empty slots like null children. That's1226 // unfortunate because it triggers the slow path all the time. We need1227 // a better way to communicate whether this was a miss or null,1228 // boolean, undefined, etc.1229 if (oldFiber === null) {1230 oldFiber = nextOldFiber;1231 }1232 break;1233 }12341235 if (__DEV__) {1236 knownKeys = warnOnInvalidKey(1237 returnFiber,1238 newFiber,1239 newChildren[newIdx],1240 knownKeys,1241 );1242 }12431244 if (shouldTrackSideEffects) {1245 if (oldFiber && newFiber.alternate === null) {1246 // We matched the slot, but we didn't reuse the existing fiber, so we1247 // need to delete the existing child.1248 deleteChild(returnFiber, oldFiber);1249 }1250 }1251 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1252 if (previousNewFiber === null) {1253 // TODO: Move out of the loop. This only happens for the first run.1254 resultingFirstChild = newFiber;1255 } else {1256 // TODO: Defer siblings if we're not at the right index for this slot.1257 // I.e. if we had null values before, then we want to defer this1258 // for each null value. However, we also don't want to call updateSlot1259 // with the previous one.1260 previousNewFiber.sibling = newFiber;1261 }1262 previousNewFiber = newFiber;1263 oldFiber = nextOldFiber;1264 }12651266 if (newIdx === newChildren.length) {1267 // We've reached the end of the new children. We can delete the rest.1268 deleteRemainingChildren(returnFiber, oldFiber);1269 if (getIsHydrating()) {1270 const numberOfForks = newIdx;1271 pushTreeFork(returnFiber, numberOfForks);1272 }1273 return resultingFirstChild;1274 }12751276 if (oldFiber === null) {1277 // If we don't have any more existing children we can choose a fast path1278 // since the rest will all be insertions.1279 for (; newIdx < newChildren.length; newIdx++) {1280 const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);1281 if (newFiber === null) {1282 continue;1283 }1284 if (__DEV__) {1285 knownKeys = warnOnInvalidKey(1286 returnFiber,1287 newFiber,1288 newChildren[newIdx],1289 knownKeys,1290 );1291 }1292 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1293 if (previousNewFiber === null) {1294 // TODO: Move out of the loop. This only happens for the first run.1295 resultingFirstChild = newFiber;1296 } else {1297 previousNewFiber.sibling = newFiber;1298 }1299 previousNewFiber = newFiber;1300 }1301 if (getIsHydrating()) {1302 const numberOfForks = newIdx;1303 pushTreeFork(returnFiber, numberOfForks);1304 }1305 return resultingFirstChild;1306 }13071308 // Add all children to a key map for quick lookups.1309 const existingChildren = mapRemainingChildren(oldFiber);13101311 // Keep scanning and use the map to restore deleted items as moves.1312 for (; newIdx < newChildren.length; newIdx++) {1313 const newFiber = updateFromMap(1314 existingChildren,1315 returnFiber,1316 newIdx,1317 newChildren[newIdx],1318 lanes,1319 );1320 if (newFiber !== null) {1321 if (__DEV__) {1322 knownKeys = warnOnInvalidKey(1323 returnFiber,1324 newFiber,1325 newChildren[newIdx],1326 knownKeys,1327 );1328 }1329 if (shouldTrackSideEffects) {1330 const currentFiber = newFiber.alternate;1331 if (currentFiber !== null) {1332 // The new fiber is a work in progress, but if there exists a1333 // current, that means that we reused the fiber. We need to delete1334 // it from the child list so that we don't add it to the deletion1335 // list.1336 if (1337 enableOptimisticKey &&1338 currentFiber.key === REACT_OPTIMISTIC_KEY1339 ) {1340 existingChildren.delete(-newIdx - 1);1341 } else {1342 existingChildren.delete(1343 currentFiber.key === null ? newIdx : currentFiber.key,1344 );1345 }1346 }1347 }1348 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1349 if (previousNewFiber === null) {1350 resultingFirstChild = newFiber;1351 } else {1352 previousNewFiber.sibling = newFiber;1353 }1354 previousNewFiber = newFiber;1355 }1356 }13571358 if (shouldTrackSideEffects) {1359 // Any existing children that weren't consumed above were deleted. We need1360 // to add them to the deletion list.1361 existingChildren.forEach(child => deleteChild(returnFiber, child));1362 }13631364 if (getIsHydrating()) {1365 const numberOfForks = newIdx;1366 pushTreeFork(returnFiber, numberOfForks);1367 }1368 return resultingFirstChild;1369 }13701371 function reconcileChildrenIteratable(1372 returnFiber: Fiber,1373 currentFirstChild: Fiber | null,1374 newChildrenIterable: Iterable<mixed>,1375 lanes: Lanes,1376 ): Fiber | null {1377 // This is the same implementation as reconcileChildrenArray(),1378 // but using the iterator instead.13791380 const iteratorFn = getIteratorFn(newChildrenIterable);13811382 if (typeof iteratorFn !== 'function') {1383 throw new Error(1384 'An object is not an iterable. This error is likely caused by a bug in ' +1385 'React. Please file an issue.',1386 );1387 }13881389 const newChildren = iteratorFn.call(newChildrenIterable);13901391 if (__DEV__) {1392 if (newChildren === newChildrenIterable) {1393 // We don't support rendering Generators as props because it's a mutation.1394 // See https://github.com/facebook/react/issues/129951395 // We do support generators if they were created by a GeneratorFunction component1396 // as its direct child since we can recreate those by rerendering the component1397 // as needed.1398 const isGeneratorComponent =1399 returnFiber.tag === FunctionComponent &&1400 // $FlowFixMe[method-unbinding]1401 Object.prototype.toString.call(returnFiber.type) ===1402 '[object GeneratorFunction]' &&1403 // $FlowFixMe[method-unbinding]1404 Object.prototype.toString.call(newChildren) === '[object Generator]';1405 if (!isGeneratorComponent) {1406 if (!didWarnAboutGenerators) {1407 console.error(1408 'Using Iterators as children is unsupported and will likely yield ' +1409 'unexpected results because enumerating a generator mutates it. ' +1410 'You may convert it to an array with `Array.from()` or the ' +1411 '`[...spread]` operator before rendering. You can also use an ' +1412 'Iterable that can iterate multiple times over the same items.',1413 );1414 }1415 didWarnAboutGenerators = true;1416 }1417 } else if ((newChildrenIterable as any).entries === iteratorFn) {1418 // Warn about using Maps as children1419 if (!didWarnAboutMaps) {1420 console.error(1421 'Using Maps as children is not supported. ' +1422 'Use an array of keyed ReactElements instead.',1423 );1424 didWarnAboutMaps = true;1425 }1426 }1427 }14281429 return reconcileChildrenIterator(1430 returnFiber,1431 currentFirstChild,1432 newChildren,1433 lanes,1434 );1435 }14361437 function reconcileChildrenAsyncIteratable(1438 returnFiber: Fiber,1439 currentFirstChild: Fiber | null,1440 newChildrenIterable: AsyncIterable<mixed>,1441 lanes: Lanes,1442 ): Fiber | null {1443 const newChildren = newChildrenIterable[ASYNC_ITERATOR]();14441445 if (__DEV__) {1446 if (newChildren === newChildrenIterable) {1447 // We don't support rendering AsyncGenerators as props because it's a mutation.1448 // We do support generators if they were created by a AsyncGeneratorFunction component1449 // as its direct child since we can recreate those by rerendering the component1450 // as needed.1451 const isGeneratorComponent =1452 returnFiber.tag === FunctionComponent &&1453 // $FlowFixMe[method-unbinding]1454 Object.prototype.toString.call(returnFiber.type) ===1455 '[object AsyncGeneratorFunction]' &&1456 // $FlowFixMe[method-unbinding]1457 Object.prototype.toString.call(newChildren) ===1458 '[object AsyncGenerator]';1459 if (!isGeneratorComponent) {1460 if (!didWarnAboutGenerators) {1461 console.error(1462 'Using AsyncIterators as children is unsupported and will likely yield ' +1463 'unexpected results because enumerating a generator mutates it. ' +1464 'You can use an AsyncIterable that can iterate multiple times over ' +1465 'the same items.',1466 );1467 }1468 didWarnAboutGenerators = true;1469 }1470 }1471 }14721473 if (newChildren == null) {1474 throw new Error('An iterable object provided no iterator.');1475 }14761477 // To save bytes, we reuse the logic by creating a synchronous Iterable and1478 // reusing that code path.1479 const iterator: Iterator<mixed> = {1480 next(): IteratorResult<mixed, void> {1481 return unwrapThenable(newChildren.next());1482 },1483 } as any;14841485 return reconcileChildrenIterator(1486 returnFiber,1487 currentFirstChild,1488 iterator,1489 lanes,1490 );1491 }14921493 function reconcileChildrenIterator(1494 returnFiber: Fiber,1495 currentFirstChild: Fiber | null,1496 newChildren: ?Iterator<mixed>,1497 lanes: Lanes,1498 ): Fiber | null {1499 if (newChildren == null) {1500 throw new Error('An iterable object provided no iterator.');1501 }15021503 let resultingFirstChild: Fiber | null = null;1504 let previousNewFiber: Fiber | null = null;15051506 let oldFiber = currentFirstChild;1507 let lastPlacedIndex = 0;1508 let newIdx = 0;1509 let nextOldFiber = null;15101511 let knownKeys: Set<string> | null = null;15121513 let step = newChildren.next();1514 for (1515 ;1516 oldFiber !== null && !step.done;1517 newIdx++, step = newChildren.next()1518 ) {1519 if (oldFiber.index > newIdx) {1520 nextOldFiber = oldFiber;1521 oldFiber = null;1522 } else {1523 nextOldFiber = oldFiber.sibling;1524 }1525 const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);1526 if (newFiber === null) {1527 // TODO: This breaks on empty slots like null children. That's1528 // unfortunate because it triggers the slow path all the time. We need1529 // a better way to communicate whether this was a miss or null,1530 // boolean, undefined, etc.1531 if (oldFiber === null) {1532 oldFiber = nextOldFiber;1533 }1534 break;1535 }15361537 if (__DEV__) {1538 knownKeys = warnOnInvalidKey(1539 returnFiber,1540 newFiber,1541 step.value,1542 knownKeys,1543 );1544 }15451546 if (shouldTrackSideEffects) {1547 if (oldFiber && newFiber.alternate === null) {1548 // We matched the slot, but we didn't reuse the existing fiber, so we1549 // need to delete the existing child.1550 deleteChild(returnFiber, oldFiber);1551 }1552 }1553 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1554 if (previousNewFiber === null) {1555 // TODO: Move out of the loop. This only happens for the first run.1556 resultingFirstChild = newFiber;1557 } else {1558 // TODO: Defer siblings if we're not at the right index for this slot.1559 // I.e. if we had null values before, then we want to defer this1560 // for each null value. However, we also don't want to call updateSlot1561 // with the previous one.1562 previousNewFiber.sibling = newFiber;1563 }1564 previousNewFiber = newFiber;1565 oldFiber = nextOldFiber;1566 }15671568 if (step.done) {1569 // We've reached the end of the new children. We can delete the rest.1570 deleteRemainingChildren(returnFiber, oldFiber);1571 if (getIsHydrating()) {1572 const numberOfForks = newIdx;1573 pushTreeFork(returnFiber, numberOfForks);1574 }1575 return resultingFirstChild;1576 }15771578 if (oldFiber === null) {1579 // If we don't have any more existing children we can choose a fast path1580 // since the rest will all be insertions.1581 for (; !step.done; newIdx++, step = newChildren.next()) {1582 const newFiber = createChild(returnFiber, step.value, lanes);1583 if (newFiber === null) {1584 continue;1585 }1586 if (__DEV__) {1587 knownKeys = warnOnInvalidKey(1588 returnFiber,1589 newFiber,1590 step.value,1591 knownKeys,1592 );1593 }1594 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1595 if (previousNewFiber === null) {1596 // TODO: Move out of the loop. This only happens for the first run.1597 resultingFirstChild = newFiber;1598 } else {1599 previousNewFiber.sibling = newFiber;1600 }1601 previousNewFiber = newFiber;1602 }1603 if (getIsHydrating()) {1604 const numberOfForks = newIdx;1605 pushTreeFork(returnFiber, numberOfForks);1606 }1607 return resultingFirstChild;1608 }16091610 // Add all children to a key map for quick lookups.1611 const existingChildren = mapRemainingChildren(oldFiber);16121613 // Keep scanning and use the map to restore deleted items as moves.1614 for (; !step.done; newIdx++, step = newChildren.next()) {1615 const newFiber = updateFromMap(1616 existingChildren,1617 returnFiber,1618 newIdx,1619 step.value,1620 lanes,1621 );1622 if (newFiber !== null) {1623 if (__DEV__) {1624 knownKeys = warnOnInvalidKey(1625 returnFiber,1626 newFiber,1627 step.value,1628 knownKeys,1629 );1630 }1631 if (shouldTrackSideEffects) {1632 const currentFiber = newFiber.alternate;1633 if (currentFiber !== null) {1634 // The new fiber is a work in progress, but if there exists a1635 // current, that means that we reused the fiber. We need to delete1636 // it from the child list so that we don't add it to the deletion1637 // list.1638 if (1639 enableOptimisticKey &&1640 currentFiber.key === REACT_OPTIMISTIC_KEY1641 ) {1642 existingChildren.delete(-newIdx - 1);1643 } else {1644 existingChildren.delete(1645 currentFiber.key === null ? newIdx : currentFiber.key,1646 );1647 }1648 }1649 }1650 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);1651 if (previousNewFiber === null) {1652 resultingFirstChild = newFiber;1653 } else {1654 previousNewFiber.sibling = newFiber;1655 }1656 previousNewFiber = newFiber;1657 }1658 }16591660 if (shouldTrackSideEffects) {1661 // Any existing children that weren't consumed above were deleted. We need1662 // to add them to the deletion list.1663 existingChildren.forEach(child => deleteChild(returnFiber, child));1664 }16651666 if (getIsHydrating()) {1667 const numberOfForks = newIdx;1668 pushTreeFork(returnFiber, numberOfForks);1669 }1670 return resultingFirstChild;1671 }16721673 function reconcileSingleTextNode(1674 returnFiber: Fiber,1675 currentFirstChild: Fiber | null,1676 textContent: string,1677 lanes: Lanes,1678 ): Fiber {1679 // There's no need to check for keys on text nodes since we don't have a1680 // way to define them.1681 if (currentFirstChild !== null && currentFirstChild.tag === HostText) {1682 // We already have an existing node so let's just update it and delete1683 // the rest.1684 deleteRemainingChildren(returnFiber, currentFirstChild.sibling);1685 const existing = useFiber(currentFirstChild, textContent);1686 existing.return = returnFiber;1687 return existing;1688 }1689 // The existing first child is not a text node so we need to create one1690 // and delete the existing ones.1691 deleteRemainingChildren(returnFiber, currentFirstChild);1692 const created = createFiberFromText(textContent, returnFiber.mode, lanes);1693 created.return = returnFiber;1694 if (__DEV__) {1695 // We treat the parent as the owner for stack purposes.1696 created._debugOwner = returnFiber;1697 created._debugTask = returnFiber._debugTask;1698 created._debugInfo = currentDebugInfo;1699 }1700 return created;1701 }17021703 function reconcileSingleElement(1704 returnFiber: Fiber,1705 currentFirstChild: Fiber | null,1706 element: ReactElement,1707 lanes: Lanes,1708 ): Fiber {1709 const key = element.key;1710 let child = currentFirstChild;1711 while (child !== null) {1712 // TODO: If key === null and child.key === null, then this only applies to1713 // the first item in the list.1714 if (1715 child.key === key ||1716 (enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)1717 ) {1718 const elementType = element.type;1719 if (elementType === REACT_FRAGMENT_TYPE) {1720 if (child.tag === Fragment) {1721 deleteRemainingChildren(returnFiber, child.sibling);1722 const existing = useFiber(child, element.props.children);1723 if (enableOptimisticKey) {1724 // If the old key was optimistic we need to now save the real one.1725 existing.key = key;1726 }1727 if (enableFragmentRefs) {1728 coerceRef(existing, element);1729 }1730 existing.return = returnFiber;1731 if (__DEV__) {1732 existing._debugOwner = element._owner;1733 existing._debugInfo = currentDebugInfo;1734 }1735 validateFragmentProps(element, existing, returnFiber);1736 return existing;1737 }1738 } else {1739 if (1740 child.elementType === elementType ||1741 // Keep this check inline so it only runs on the false path:1742 (__DEV__1743 ? isCompatibleFamilyForHotReloading(child, element)1744 : false) ||1745 // Lazy types should reconcile their resolved type.1746 // We need to do this after the Hot Reloading check above,1747 // because hot reloading has different semantics than prod because1748 // it doesn't resuspend. So we can't let the call below suspend.1749 (typeof elementType === 'object' &&1750 elementType !== null &&1751 elementType.$$typeof === REACT_LAZY_TYPE &&1752 resolveLazy(elementType) === child.type)1753 ) {1754 deleteRemainingChildren(returnFiber, child.sibling);1755 const existing = useFiber(child, element.props);1756 if (enableOptimisticKey) {1757 // If the old key was optimistic we need to now save the real one.1758 existing.key = key;1759 }1760 coerceRef(existing, element);1761 existing.return = returnFiber;1762 if (__DEV__) {1763 existing._debugOwner = element._owner;1764 existing._debugInfo = currentDebugInfo;1765 }1766 return existing;1767 }1768 }1769 // Didn't match.1770 deleteRemainingChildren(returnFiber, child);1771 break;1772 } else {1773 deleteChild(returnFiber, child);1774 }1775 child = child.sibling;1776 }17771778 if (element.type === REACT_FRAGMENT_TYPE) {1779 const created = createFiberFromFragment(1780 element.props.children,1781 returnFiber.mode,1782 lanes,1783 element.key,1784 );1785 if (enableFragmentRefs) {1786 coerceRef(created, element);1787 }1788 created.return = returnFiber;1789 if (__DEV__) {1790 // We treat the parent as the owner for stack purposes.1791 created._debugOwner = returnFiber;1792 created._debugTask = returnFiber._debugTask;1793 created._debugInfo = currentDebugInfo;1794 }1795 validateFragmentProps(element, created, returnFiber);1796 return created;1797 } else {1798 const created = createFiberFromElement(element, returnFiber.mode, lanes);1799 coerceRef(created, element);1800 created.return = returnFiber;1801 if (__DEV__) {1802 created._debugInfo = currentDebugInfo;1803 }1804 return created;1805 }1806 }18071808 function reconcileSinglePortal(1809 returnFiber: Fiber,1810 currentFirstChild: Fiber | null,1811 portal: ReactPortal,1812 lanes: Lanes,1813 ): Fiber {1814 const key = portal.key;1815 let child = currentFirstChild;1816 while (child !== null) {1817 // TODO: If key === null and child.key === null, then this only applies to1818 // the first item in the list.1819 if (1820 child.key === key ||1821 (enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)1822 ) {1823 if (1824 child.tag === HostPortal &&1825 child.stateNode.containerInfo === portal.containerInfo &&1826 child.stateNode.implementation === portal.implementation1827 ) {1828 deleteRemainingChildren(returnFiber, child.sibling);1829 const existing = useFiber(child, portal.children || []);1830 if (enableOptimisticKey) {1831 // If the old key was optimistic we need to now save the real one.1832 existing.key = key;1833 }1834 existing.return = returnFiber;1835 return existing;1836 } else {1837 deleteRemainingChildren(returnFiber, child);1838 break;1839 }1840 } else {1841 deleteChild(returnFiber, child);1842 }1843 child = child.sibling;1844 }18451846 const created = createFiberFromPortal(portal, returnFiber.mode, lanes);1847 created.return = returnFiber;1848 return created;1849 }18501851 // This API will tag the children with the side-effect of the reconciliation1852 // itself. They will be added to the side-effect list as we pass through the1853 // children and the parent.1854 function reconcileChildFibersImpl(1855 returnFiber: Fiber,1856 currentFirstChild: Fiber | null,1857 newChild: any,1858 lanes: Lanes,1859 ): Fiber | null {1860 // This function is only recursive for Usables/Lazy and not nested arrays.1861 // That's so that using a Lazy wrapper is unobservable to the Fragment1862 // convention.1863 // If the top level item is an array, we treat it as a set of children,1864 // not as a fragment. Nested arrays on the other hand will be treated as1865 // fragment nodes. Recursion happens at the normal flow.18661867 // Handle top level unkeyed fragments without refs (enableFragmentRefs)1868 // as if they were arrays. This leads to an ambiguity between <>{[...]}</> and <>...</>.1869 // We treat the ambiguous cases above the same.1870 // We don't use recursion here because a fragment inside a fragment1871 // is no longer considered "top level" for these purposes.1872 const isUnkeyedUnrefedTopLevelFragment =1873 typeof newChild === 'object' &&1874 newChild !== null &&1875 newChild.type === REACT_FRAGMENT_TYPE &&1876 newChild.key === null &&1877 (enableFragmentRefs ? newChild.props.ref === undefined : true);18781879 if (isUnkeyedUnrefedTopLevelFragment) {1880 validateFragmentProps(newChild, null, returnFiber);1881 newChild = newChild.props.children;1882 }18831884 // Handle object types1885 if (typeof newChild === 'object' && newChild !== null) {1886 switch (newChild.$$typeof) {1887 case REACT_ELEMENT_TYPE: {1888 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);1889 const firstChild = placeSingleChild(1890 reconcileSingleElement(1891 returnFiber,1892 currentFirstChild,1893 newChild,1894 lanes,1895 ),1896 );1897 currentDebugInfo = prevDebugInfo;1898 return firstChild;1899 }1900 case REACT_PORTAL_TYPE:1901 return placeSingleChild(1902 reconcileSinglePortal(1903 returnFiber,1904 currentFirstChild,1905 newChild,1906 lanes,1907 ),1908 );1909 case REACT_LAZY_TYPE: {1910 const prevDebugInfo = pushDebugInfo(newChild._debugInfo);1911 const result = resolveLazy(newChild as any);1912 const firstChild = reconcileChildFibersImpl(1913 returnFiber,1914 currentFirstChild,1915 result,1916 lanes,1917 );1918 currentDebugInfo = prevDebugInfo;1919 return firstChild;1920 }1921 }19221923 if (isArray(newChild)) {1924 // We created a Fragment for this child with the debug info.1925 // No need to push again.1926 const firstChild = reconcileChildrenArray(1927 returnFiber,1928 currentFirstChild,1929 newChild,1930 lanes,1931 );1932 return firstChild;1933 }19341935 if (getIteratorFn(newChild)) {1936 // We created a Fragment for this child with the debug info.1937 // No need to push again.1938 const firstChild = reconcileChildrenIteratable(1939 returnFiber,1940 currentFirstChild,1941 newChild,1942 lanes,1943 );1944 return firstChild;1945 }19461947 if (1948 enableAsyncIterableChildren &&1949 typeof newChild[ASYNC_ITERATOR] === 'function'1950 ) {1951 // We created a Fragment for this child with the debug info.1952 // No need to push again.1953 const firstChild = reconcileChildrenAsyncIteratable(1954 returnFiber,1955 currentFirstChild,1956 newChild,1957 lanes,1958 );1959 return firstChild;1960 }19611962 // Usables are a valid React node type. When React encounters a Usable in1963 // a child position, it unwraps it using the same algorithm as `use`. For1964 // example, for promises, React will throw an exception to unwind the1965 // stack, then replay the component once the promise resolves.1966 //1967 // A difference from `use` is that React will keep unwrapping the value1968 // until it reaches a non-Usable type.1969 //1970 // e.g. Usable<Usable<Usable<T>>> should resolve to T1971 //1972 // The structure is a bit unfortunate. Ideally, we shouldn't need to1973 // replay the entire begin phase of the parent fiber in order to reconcile1974 // the children again. This would require a somewhat significant refactor,1975 // because reconcilation happens deep within the begin phase, and1976 // depending on the type of work, not always at the end. We should1977 // consider as an future improvement.1978 if (typeof newChild.then === 'function') {1979 const thenable: Thenable<any> = newChild as any;1980 const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);1981 const firstChild = reconcileChildFibersImpl(1982 returnFiber,1983 currentFirstChild,1984 unwrapThenable(thenable),1985 lanes,1986 );1987 currentDebugInfo = prevDebugInfo;1988 return firstChild;1989 }19901991 // $FlowFixMe[invalid-compare]1992 if (newChild.$$typeof === REACT_CONTEXT_TYPE) {1993 const context: ReactContext<mixed> = newChild as any;1994 return reconcileChildFibersImpl(1995 returnFiber,1996 currentFirstChild,1997 readContextDuringReconciliation(returnFiber, context, lanes),1998 lanes,1999 );2000 }
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.