main
js 2,259 lines 72.2 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {ReactElement} from 'shared/ReactElementType';
11 import type {
12 ReactPortal,
13 Thenable,
14 ReactContext,
15 ReactDebugInfo,
16 ReactComponentInfo,
17 SuspenseListRevealOrder,
18 ReactKey,
19 ReactOptimisticKey,
20 } from 'shared/ReactTypes';
21 import type {Fiber} from './ReactInternalTypes';
22 import type {Lanes} from './ReactFiberLane';
23 import type {ThenableState} from './ReactFiberThenable';
24
25 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
26 import {
27 Placement,
28 ChildDeletion,
29 Forked,
30 PlacementDEV,
31 } from './ReactFiberFlags';
32 import {NoMode, ConcurrentMode} from './ReactTypeOfMode';
33 import {
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';
44 import {
45 HostRoot,
46 HostText,
47 HostPortal,
48 Fragment,
49 FunctionComponent,
50 } from './ReactWorkTags';
51 import isArray from 'shared/isArray';
52 import {
53 enableAsyncIterableChildren,
54 disableLegacyMode,
55 enableFragmentRefs,
56 enableOptimisticKey,
57 } from 'shared/ReactFeatureFlags';
58
59 import {
60 createWorkInProgress,
61 resetWorkInProgress,
62 createFiberFromElement,
63 createFiberFromFragment,
64 createFiberFromText,
65 createFiberFromPortal,
66 createFiberFromThrow,
67 } from './ReactFiber';
68 import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
69 import {getIsHydrating} from './ReactFiberHydrationContext';
70 import {pushTreeFork} from './ReactFiberTreeContext';
71 import {
72 SuspenseException,
73 SuspenseActionException,
74 createThenableState,
75 trackUsedThenable,
76 resolveLazy,
77 } from './ReactFiberThenable';
78 import {readContextDuringReconciliation} from './ReactFiberNewContext';
79
80 import {runWithFiberInDEV} from './ReactCurrentFiber';
81
82 // This tracks the thenables that are unwrapped during reconcilation.
83 let thenableState: ThenableState | null = null;
84 let thenableIndexCounter: number = 0;
85
86 // Server Components Meta Data
87 let currentDebugInfo: null | ReactDebugInfo = null;
88
89 function 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 inplace
98 } 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 longer
102 // live so we'll miss any future updates if we received more so ideally we should always
103 // do this after both have fully resolved/unsuspended.
104 currentDebugInfo = previousDebugInfo.concat(debugInfo);
105 }
106 return previousDebugInfo;
107 }
108
109 function 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 }
127
128 let didWarnAboutMaps;
129 let didWarnAboutGenerators;
130 let ownerHasKeyUseWarning;
131 let ownerHasFunctionTypeWarning;
132 let ownerHasSymbolTypeWarning;
133 let warnForMissingKey = (
134 returnFiber: Fiber,
135 workInProgress: Fiber,
136 child: mixed,
137 ) => {};
138
139 if (__DEV__) {
140 didWarnAboutMaps = false;
141 didWarnAboutGenerators = false;
142
143 /**
144 * Warn if there's no key explicitly set on dynamic arrays of children or
145 * object keys are not valid. This allows us to keep track of children between
146 * updates.
147 */
148 ownerHasKeyUseWarning = {} as {[string]: boolean};
149 ownerHasFunctionTypeWarning = {} as {[string]: boolean};
150 ownerHasSymbolTypeWarning = {} as {[string]: boolean};
151
152 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 }
167
168 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 }
174
175 // $FlowFixMe[cannot-write] unable to narrow type from mixed to writable object
176 child._store.validated = 1;
177
178 const componentName = getComponentNameFromFiber(returnFiber);
179
180 const componentKey = componentName || 'null';
181 if (ownerHasKeyUseWarning[componentKey]) {
182 return;
183 }
184 ownerHasKeyUseWarning[componentKey] = true;
185
186 const childOwner = child._owner;
187 const parentOwner = returnFiber._debugOwner;
188
189 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 }
202
203 // Usually the current owner is the offender, but if it accepts children as a
204 // property, it may be the creator of the child that's responsible for
205 // 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 }
219
220 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 }
230
231 // Given a fragment, validate that it can only be provided with fragment props
232 // We do this here instead of BeginWork because the Fragment fiber doesn't have
233 // the whole props object, only the children and is shared with arrays.
234 function 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 }
281
282 function 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 }
290
291 function 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 component
294 // 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. We
297 // should always read the ref from the prop.
298 workInProgress.ref = refProp !== undefined ? refProp : null;
299 }
300
301 function 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 }
311
312 // $FlowFixMe[method-unbinding]
313 const childString = Object.prototype.toString.call(newChild);
314
315 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 : childString
320 }). ` +
321 'If you meant to render a collection of children, use an array ' +
322 'instead.',
323 );
324 }
325
326 function 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 }
336
337 function warnOnFunctionTypeImpl(returnFiber: Fiber, invalidChild: Function) {
338 if (__DEV__) {
339 const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
340
341 if (ownerHasFunctionTypeWarning[parentName]) {
342 return;
343 }
344 ownerHasFunctionTypeWarning[parentName] = true;
345
346 const name = invalidChild.displayName || invalidChild.name || 'Component';
347
348 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 }
373
374 function 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 }
382
383 function warnOnSymbolTypeImpl(returnFiber: Fiber, invalidChild: symbol) {
384 if (__DEV__) {
385 const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
386
387 if (ownerHasSymbolTypeWarning[parentName]) {
388 return;
389 }
390 ownerHasSymbolTypeWarning[parentName] = true;
391
392 // eslint-disable-next-line react-internal/safe-string-coercion
393 const name = String(invalidChild);
394
395 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 }
410
411 function 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 }
419
420 type ChildReconciler = (
421 returnFiber: Fiber,
422 currentFirstChild: Fiber | null,
423 newChild: any,
424 lanes: Lanes,
425 ) => Fiber | null;
426
427 // This wrapper function exists because I expect to clone the code in each path
428 // to be able to optimize each path individually by branching early. This needs
429 // a compiler or we can do it manually. Helpers that don't need this branching
430 // live outside of this function.
431 function 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 }
447
448 function deleteRemainingChildren(
449 returnFiber: Fiber,
450 currentFirstChild: Fiber | null,
451 ): null {
452 if (!shouldTrackSideEffects) {
453 // Noop.
454 return null;
455 }
456
457 // TODO: For the shouldClone case, this could be micro-optimized a bit by
458 // 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 }
466
467 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 by
471 // keys quickly. Implicit (null) keys get added to this set with their index
472 // instead.
473 const existingChildren: Map<
474 | string
475 | number
476 // 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();
481
482 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_KEY
489 ) {
490 // For optimistic keys, we store the negative index (minus one) to differentiate
491 // them from the regular indices. We'll look this up regardless of what the new
492 // 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 }
501
502 function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
503 // We currently set sibling to null and index to 0 here because it is easy
504 // 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 }
510
511 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 are
519 // 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 new
528 // mount; don't set PlacementDEV, which would cause StrictMode to
529 // 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 }
542
543 function placeSingleChild(newFiber: Fiber): Fiber {
544 // This is simpler for the single child case. We only need to do a
545 // placement for inserting new children.
546 if (shouldTrackSideEffects && newFiber.alternate === null) {
547 newFiber.flags |= Placement | PlacementDEV;
548 }
549 return newFiber;
550 }
551
552 function updateTextNode(
553 returnFiber: Fiber,
554 current: Fiber | null,
555 textContent: string,
556 lanes: Lanes,
557 ) {
558 if (current === null || current.tag !== HostText) {
559 // Insert
560 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 // Update
571 const existing = useFiber(current, textContent);
572 existing.return = returnFiber;
573 if (__DEV__) {
574 existing._debugInfo = currentDebugInfo;
575 }
576 return existing;
577 }
578 }
579
580 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 because
611 // 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 index
618 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 // Insert
629 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 }
637
638 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.implementation
649 ) {
650 // Insert
651 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 // Update
659 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 }
671
672 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 // Insert
681 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 // Update
697 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 }
709
710 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 keyed
721 // we can continue to replace it without aborting even if it is not a text
722 // node.
723 const created = createFiberFromText(
724 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
725 '' + 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 }
738
739 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 }
776
777 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 }
801
802 // Usable node types
803 //
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 }
816
817 // $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 }
826
827 throwOnInvalidObjectType(returnFiber, newChild);
828 }
829
830 if (__DEV__) {
831 if (typeof newChild === 'function') {
832 warnOnFunctionType(returnFiber, newChild);
833 }
834 if (typeof newChild === 'symbol') {
835 warnOnSymbolType(returnFiber, newChild);
836 }
837 }
838
839 return null;
840 }
841
842 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;
850
851 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 keyed
857 // we can continue to replace it without aborting even if it is not a text
858 // 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 bigint
866 '' + newChild,
867 lanes,
868 );
869 }
870
871 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 that
878 // takes precedence over assuming the identity of an optimistic slot.
879 newChild.key === key
880 ) {
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 that
899 // takes precedence over assuming the identity of an optimistic slot.
900 newChild.key === key
901 ) {
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 }
920
921 if (
922 isArray(newChild) ||
923 getIteratorFn(newChild) ||
924 (enableAsyncIterableChildren &&
925 typeof newChild[ASYNC_ITERATOR] === 'function')
926 ) {
927 if (key !== null) {
928 return null;
929 }
930
931 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 }
942
943 // Usable node types
944 //
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 }
958
959 // $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 }
969
970 throwOnInvalidObjectType(returnFiber, newChild);
971 }
972
973 if (__DEV__) {
974 if (typeof newChild === 'function') {
975 warnOnFunctionType(returnFiber, newChild);
976 }
977 if (typeof newChild === 'symbol') {
978 warnOnSymbolType(returnFiber, newChild);
979 }
980 }
981
982 return null;
983 }
984
985 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 nor
998 // 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 bigint
1004 '' + newChild,
1005 lanes,
1006 );
1007 }
1008
1009 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 }
1055
1056 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 }
1074
1075 // Usable node types
1076 //
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 }
1091
1092 // $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 }
1103
1104 throwOnInvalidObjectType(returnFiber, newChild);
1105 }
1106
1107 if (__DEV__) {
1108 if (typeof newChild === 'function') {
1109 warnOnFunctionType(returnFiber, newChild);
1110 }
1111 if (typeof newChild === 'symbol') {
1112 warnOnSymbolType(returnFiber, newChild);
1113 }
1114 }
1115
1116 return null;
1117 }
1118
1119 /**
1120 * Warns if there is a duplicate or missing key
1121 */
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 }
1176
1177 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 we
1184 // don't have backpointers on fibers. I'm trying to see how far we can get
1185 // with that model. If it ends up not being worth the tradeoffs, we can
1186 // add it later.
1187
1188 // Even with a two ended optimization, we'd want to optimize for the case
1189 // where there are few changes and brute force the comparison instead of
1190 // going for the Map. It'd like to explore hitting that path first in
1191 // forward-only mode and only go for the Map once we notice that we need
1192 // lots of look ahead. This doesn't handle reversal as well as two ended
1193 // search but that's unusual. Besides, for the two ended optimization to
1194 // work on Iterables, we'd need to copy the whole set.
1195
1196 // In this first iteration, we'll just live with hitting the bad case
1197 // (adding everything to a Map) in for every insert/move.
1198
1199 // If you change this code, also update reconcileChildrenIterator() which
1200 // uses the same algorithm.
1201
1202 let knownKeys: Set<string> | null = null;
1203
1204 let resultingFirstChild: Fiber | null = null;
1205 let previousNewFiber: Fiber | null = null;
1206
1207 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's
1226 // unfortunate because it triggers the slow path all the time. We need
1227 // 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 }
1234
1235 if (__DEV__) {
1236 knownKeys = warnOnInvalidKey(
1237 returnFiber,
1238 newFiber,
1239 newChildren[newIdx],
1240 knownKeys,
1241 );
1242 }
1243
1244 if (shouldTrackSideEffects) {
1245 if (oldFiber && newFiber.alternate === null) {
1246 // We matched the slot, but we didn't reuse the existing fiber, so we
1247 // 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 this
1258 // for each null value. However, we also don't want to call updateSlot
1259 // with the previous one.
1260 previousNewFiber.sibling = newFiber;
1261 }
1262 previousNewFiber = newFiber;
1263 oldFiber = nextOldFiber;
1264 }
1265
1266 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 }
1275
1276 if (oldFiber === null) {
1277 // If we don't have any more existing children we can choose a fast path
1278 // 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 }
1307
1308 // Add all children to a key map for quick lookups.
1309 const existingChildren = mapRemainingChildren(oldFiber);
1310
1311 // 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 a
1333 // current, that means that we reused the fiber. We need to delete
1334 // it from the child list so that we don't add it to the deletion
1335 // list.
1336 if (
1337 enableOptimisticKey &&
1338 currentFiber.key === REACT_OPTIMISTIC_KEY
1339 ) {
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 }
1357
1358 if (shouldTrackSideEffects) {
1359 // Any existing children that weren't consumed above were deleted. We need
1360 // to add them to the deletion list.
1361 existingChildren.forEach(child => deleteChild(returnFiber, child));
1362 }
1363
1364 if (getIsHydrating()) {
1365 const numberOfForks = newIdx;
1366 pushTreeFork(returnFiber, numberOfForks);
1367 }
1368 return resultingFirstChild;
1369 }
1370
1371 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.
1379
1380 const iteratorFn = getIteratorFn(newChildrenIterable);
1381
1382 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 }
1388
1389 const newChildren = iteratorFn.call(newChildrenIterable);
1390
1391 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/12995
1395 // We do support generators if they were created by a GeneratorFunction component
1396 // as its direct child since we can recreate those by rerendering the component
1397 // 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 children
1419 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 }
1428
1429 return reconcileChildrenIterator(
1430 returnFiber,
1431 currentFirstChild,
1432 newChildren,
1433 lanes,
1434 );
1435 }
1436
1437 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]();
1444
1445 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 component
1449 // as its direct child since we can recreate those by rerendering the component
1450 // 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 }
1472
1473 if (newChildren == null) {
1474 throw new Error('An iterable object provided no iterator.');
1475 }
1476
1477 // To save bytes, we reuse the logic by creating a synchronous Iterable and
1478 // reusing that code path.
1479 const iterator: Iterator<mixed> = {
1480 next(): IteratorResult<mixed, void> {
1481 return unwrapThenable(newChildren.next());
1482 },
1483 } as any;
1484
1485 return reconcileChildrenIterator(
1486 returnFiber,
1487 currentFirstChild,
1488 iterator,
1489 lanes,
1490 );
1491 }
1492
1493 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 }
1502
1503 let resultingFirstChild: Fiber | null = null;
1504 let previousNewFiber: Fiber | null = null;
1505
1506 let oldFiber = currentFirstChild;
1507 let lastPlacedIndex = 0;
1508 let newIdx = 0;
1509 let nextOldFiber = null;
1510
1511 let knownKeys: Set<string> | null = null;
1512
1513 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's
1528 // unfortunate because it triggers the slow path all the time. We need
1529 // 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 }
1536
1537 if (__DEV__) {
1538 knownKeys = warnOnInvalidKey(
1539 returnFiber,
1540 newFiber,
1541 step.value,
1542 knownKeys,
1543 );
1544 }
1545
1546 if (shouldTrackSideEffects) {
1547 if (oldFiber && newFiber.alternate === null) {
1548 // We matched the slot, but we didn't reuse the existing fiber, so we
1549 // 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 this
1560 // for each null value. However, we also don't want to call updateSlot
1561 // with the previous one.
1562 previousNewFiber.sibling = newFiber;
1563 }
1564 previousNewFiber = newFiber;
1565 oldFiber = nextOldFiber;
1566 }
1567
1568 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 }
1577
1578 if (oldFiber === null) {
1579 // If we don't have any more existing children we can choose a fast path
1580 // 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 }
1609
1610 // Add all children to a key map for quick lookups.
1611 const existingChildren = mapRemainingChildren(oldFiber);
1612
1613 // 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 a
1635 // current, that means that we reused the fiber. We need to delete
1636 // it from the child list so that we don't add it to the deletion
1637 // list.
1638 if (
1639 enableOptimisticKey &&
1640 currentFiber.key === REACT_OPTIMISTIC_KEY
1641 ) {
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 }
1659
1660 if (shouldTrackSideEffects) {
1661 // Any existing children that weren't consumed above were deleted. We need
1662 // to add them to the deletion list.
1663 existingChildren.forEach(child => deleteChild(returnFiber, child));
1664 }
1665
1666 if (getIsHydrating()) {
1667 const numberOfForks = newIdx;
1668 pushTreeFork(returnFiber, numberOfForks);
1669 }
1670 return resultingFirstChild;
1671 }
1672
1673 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 a
1680 // 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 delete
1683 // 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 one
1690 // 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 }
1702
1703 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 to
1713 // 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 because
1748 // 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 }
1777
1778 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 }
1807
1808 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 to
1818 // 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.implementation
1827 ) {
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 }
1845
1846 const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
1847 created.return = returnFiber;
1848 return created;
1849 }
1850
1851 // This API will tag the children with the side-effect of the reconciliation
1852 // itself. They will be added to the side-effect list as we pass through the
1853 // 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 Fragment
1862 // 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 as
1865 // fragment nodes. Recursion happens at the normal flow.
1866
1867 // 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 fragment
1871 // 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);
1878
1879 if (isUnkeyedUnrefedTopLevelFragment) {
1880 validateFragmentProps(newChild, null, returnFiber);
1881 newChild = newChild.props.children;
1882 }
1883
1884 // Handle object types
1885 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 }
1922
1923 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 }
1934
1935 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 }
1946
1947 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 }
1961
1962 // Usables are a valid React node type. When React encounters a Usable in
1963 // a child position, it unwraps it using the same algorithm as `use`. For
1964 // example, for promises, React will throw an exception to unwind the
1965 // stack, then replay the component once the promise resolves.
1966 //
1967 // A difference from `use` is that React will keep unwrapping the value
1968 // until it reaches a non-Usable type.
1969 //
1970 // e.g. Usable<Usable<Usable<T>>> should resolve to T
1971 //
1972 // The structure is a bit unfortunate. Ideally, we shouldn't need to
1973 // replay the entire begin phase of the parent fiber in order to reconcile
1974 // the children again. This would require a somewhat significant refactor,
1975 // because reconcilation happens deep within the begin phase, and
1976 // depending on the type of work, not always at the end. We should
1977 // 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 }
1990
1991 // $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 }
2001
2002 throwOnInvalidObjectType(returnFiber, newChild);
2003 }
2004
2005 if (
2006 (typeof newChild === 'string' && newChild !== '') ||
2007 typeof newChild === 'number' ||
2008 typeof newChild === 'bigint'
2009 ) {
2010 return placeSingleChild(
2011 reconcileSingleTextNode(
2012 returnFiber,
2013 currentFirstChild,
2014 // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
2015 '' + newChild,
2016 lanes,
2017 ),
2018 );
2019 }
2020
2021 if (__DEV__) {
2022 if (typeof newChild === 'function') {
2023 warnOnFunctionType(returnFiber, newChild);
2024 }
2025 if (typeof newChild === 'symbol') {
2026 warnOnSymbolType(returnFiber, newChild);
2027 }
2028 }
2029
2030 // Remaining cases are all treated as empty.
2031 return deleteRemainingChildren(returnFiber, currentFirstChild);
2032 }
2033
2034 function reconcileChildFibers(
2035 returnFiber: Fiber,
2036 currentFirstChild: Fiber | null,
2037 newChild: any,
2038 lanes: Lanes,
2039 ): Fiber | null {
2040 const prevDebugInfo = currentDebugInfo;
2041 currentDebugInfo = null;
2042 try {
2043 // This indirection only exists so we can reset `thenableState` at the end.
2044 // It should get inlined by Closure.
2045 thenableIndexCounter = 0;
2046 const firstChildFiber = reconcileChildFibersImpl(
2047 returnFiber,
2048 currentFirstChild,
2049 newChild,
2050 lanes,
2051 );
2052 thenableState = null;
2053 // Don't bother to reset `thenableIndexCounter` to 0 because it always gets
2054 // set at the beginning.
2055 return firstChildFiber;
2056 } catch (x) {
2057 if (
2058 x === SuspenseException ||
2059 x === SuspenseActionException ||
2060 (!disableLegacyMode &&
2061 (returnFiber.mode & ConcurrentMode) === NoMode &&
2062 typeof x === 'object' &&
2063 x !== null &&
2064 typeof x.then === 'function')
2065 ) {
2066 // Suspense exceptions need to read the current suspended state before
2067 // yielding and replay it using the same sequence so this trick doesn't
2068 // work here.
2069 // Suspending in legacy mode actually mounts so if we let the child
2070 // mount then we delete its state in an update.
2071 throw x;
2072 }
2073 // Something errored during reconciliation but it's conceptually a child that
2074 // errored and not the current component itself so we create a virtual child
2075 // that throws in its begin phase. That way the current component can handle
2076 // the error or suspending if needed.
2077 const throwFiber = createFiberFromThrow(x, returnFiber.mode, lanes);
2078 throwFiber.return = returnFiber;
2079 if (__DEV__) {
2080 const debugInfo = (throwFiber._debugInfo = currentDebugInfo);
2081 // Conceptually the error's owner should ideally be captured when the
2082 // Error constructor is called but we don't override them to capture our
2083 // `owner`. So instead, we use the nearest parent as the owner/task of the
2084 // error. This is usually the same thing when it's thrown from the same
2085 // async component but not if you await a promise started from a different
2086 // component/task.
2087 // In newer Chrome, Error constructor does capture the Task which is what
2088 // is logged by reportError. In that case this debugTask isn't used.
2089 throwFiber._debugOwner = returnFiber._debugOwner;
2090 throwFiber._debugTask = returnFiber._debugTask;
2091 if (debugInfo != null) {
2092 for (let i = debugInfo.length - 1; i >= 0; i--) {
2093 if (typeof debugInfo[i].stack === 'string') {
2094 throwFiber._debugOwner = debugInfo[i] as any;
2095 throwFiber._debugTask = debugInfo[i].debugTask;
2096 break;
2097 }
2098 }
2099 }
2100 }
2101 return throwFiber;
2102 } finally {
2103 currentDebugInfo = prevDebugInfo;
2104 }
2105 }
2106
2107 return reconcileChildFibers;
2108 }
2109
2110 export const reconcileChildFibers: ChildReconciler =
2111 createChildReconciler(true);
2112 export const mountChildFibers: ChildReconciler = createChildReconciler(false);
2113
2114 export function resetChildReconcilerOnUnwind(): void {
2115 // On unwind, clear any pending thenables that were used.
2116 thenableState = null;
2117 thenableIndexCounter = 0;
2118 }
2119
2120 export function cloneChildFibers(
2121 current: Fiber | null,
2122 workInProgress: Fiber,
2123 ): void {
2124 if (current !== null && workInProgress.child !== current.child) {
2125 throw new Error('Resuming work not yet implemented.');
2126 }
2127
2128 if (workInProgress.child === null) {
2129 return;
2130 }
2131
2132 let currentChild = workInProgress.child;
2133 let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
2134 workInProgress.child = newChild;
2135
2136 newChild.return = workInProgress;
2137 while (currentChild.sibling !== null) {
2138 currentChild = currentChild.sibling;
2139 newChild = newChild.sibling = createWorkInProgress(
2140 currentChild,
2141 currentChild.pendingProps,
2142 );
2143 newChild.return = workInProgress;
2144 }
2145 newChild.sibling = null;
2146 }
2147
2148 // Reset a workInProgress child set to prepare it for a second pass.
2149 export function resetChildFibers(workInProgress: Fiber, lanes: Lanes): void {
2150 let child = workInProgress.child;
2151 while (child !== null) {
2152 resetWorkInProgress(child, lanes);
2153 child = child.sibling;
2154 }
2155 }
2156
2157 function validateSuspenseListNestedChild(childSlot: mixed, index: number) {
2158 if (__DEV__) {
2159 const isAnArray = isArray(childSlot);
2160 const isIterable =
2161 !isAnArray && typeof getIteratorFn(childSlot) === 'function';
2162 const isAsyncIterable =
2163 enableAsyncIterableChildren &&
2164 typeof childSlot === 'object' &&
2165 childSlot !== null &&
2166 typeof (childSlot as any)[ASYNC_ITERATOR] === 'function';
2167 if (isAnArray || isIterable || isAsyncIterable) {
2168 const type = isAnArray
2169 ? 'array'
2170 : isAsyncIterable
2171 ? 'async iterable'
2172 : 'iterable';
2173 console.error(
2174 'A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' +
2175 'an additional SuspenseList to configure its revealOrder: ' +
2176 '<SuspenseList revealOrder=...> ... ' +
2177 '<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' +
2178 '</SuspenseList>',
2179 type,
2180 index,
2181 type,
2182 );
2183 return false;
2184 }
2185 }
2186 return true;
2187 }
2188
2189 export function validateSuspenseListChildren(
2190 children: mixed,
2191 revealOrder: SuspenseListRevealOrder,
2192 ) {
2193 if (__DEV__) {
2194 if (
2195 (revealOrder == null ||
2196 revealOrder === 'forwards' ||
2197 revealOrder === 'backwards' ||
2198 revealOrder === 'unstable_legacy-backwards') &&
2199 children !== undefined &&
2200 children !== null &&
2201 children !== false
2202 ) {
2203 if (isArray(children)) {
2204 for (let i = 0; i < children.length; i++) {
2205 if (!validateSuspenseListNestedChild(children[i], i)) {
2206 return;
2207 }
2208 }
2209 } else {
2210 const iteratorFn = getIteratorFn(children);
2211 if (typeof iteratorFn === 'function') {
2212 const childrenIterator = iteratorFn.call(children);
2213 if (childrenIterator) {
2214 let step = childrenIterator.next();
2215 let i = 0;
2216 for (; !step.done; step = childrenIterator.next()) {
2217 if (!validateSuspenseListNestedChild(step.value, i)) {
2218 return;
2219 }
2220 i++;
2221 }
2222 }
2223 } else if (
2224 enableAsyncIterableChildren &&
2225 typeof (children as any)[ASYNC_ITERATOR] === 'function'
2226 ) {
2227 // TODO: Technically we should warn for nested arrays inside the
2228 // async iterable but it would require unwrapping the array.
2229 // However, this mistake is not as easy to make so it's ok not to warn.
2230 } else if (
2231 enableAsyncIterableChildren &&
2232 children.$$typeof === REACT_ELEMENT_TYPE &&
2233 typeof children.type === 'function' &&
2234 // $FlowFixMe[method-unbinding]
2235 (Object.prototype.toString.call(children.type) ===
2236 '[object GeneratorFunction]' ||
2237 // $FlowFixMe[incompatible-use]
2238 // $FlowFixMe[method-unbinding]
2239 Object.prototype.toString.call(children.type) ===
2240 '[object AsyncGeneratorFunction]')
2241 ) {
2242 console.error(
2243 'A generator Component was passed to a <SuspenseList revealOrder="%s" />. ' +
2244 'This is not supported as a way to generate lists. Instead, pass an ' +
2245 'iterable as the children.',
2246 revealOrder,
2247 );
2248 } else {
2249 console.error(
2250 'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
2251 'This is not useful since it needs multiple rows. ' +
2252 'Did you mean to pass multiple children or an array?',
2253 revealOrder,
2254 );
2255 }
2256 }
2257 }
2258 }
2259 }