main
js 4,514 lines 152 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 {
11 ReactConsumerType,
12 ReactContext,
13 ReactNodeList,
14 ViewTransitionProps,
15 ActivityProps,
16 SuspenseProps,
17 SuspenseListProps,
18 SuspenseListRevealOrder,
19 SuspenseListTailMode,
20 TracingMarkerProps,
21 CacheProps,
22 ProfilerProps,
23 } from 'shared/ReactTypes';
24 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
25 import type {Fiber, FiberRoot} from './ReactInternalTypes';
26 import type {TypeOfMode} from './ReactTypeOfMode';
27 import type {Lanes, Lane} from './ReactFiberLane';
28 import type {ActivityState} from './ReactFiberActivityComponent';
29 import type {
30 SuspenseState,
31 SuspenseListRenderState,
32 } from './ReactFiberSuspenseComponent';
33 import type {SuspenseContext} from './ReactFiberSuspenseContext';
34 import type {
35 LegacyHiddenProps,
36 OffscreenProps,
37 OffscreenState,
38 OffscreenQueue,
39 OffscreenInstance,
40 } from './ReactFiberOffscreenComponent';
41 import type {
42 Cache,
43 CacheComponentState,
44 SpawnedCachePool,
45 } from './ReactFiberCacheComponent';
46 import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
47 import type {RootState} from './ReactFiberRoot';
48 import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
49 import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
50
51 import {
52 markComponentRenderStarted,
53 markComponentRenderStopped,
54 setIsStrictModeForDevtools,
55 } from './ReactFiberDevToolsHook';
56 import {
57 FunctionComponent,
58 ClassComponent,
59 HostRoot,
60 HostComponent,
61 HostHoistable,
62 HostSingleton,
63 HostText,
64 HostPortal,
65 ForwardRef,
66 Fragment,
67 Mode,
68 ContextProvider,
69 ContextConsumer,
70 Profiler,
71 SuspenseComponent,
72 SuspenseListComponent,
73 MemoComponent,
74 SimpleMemoComponent,
75 LazyComponent,
76 IncompleteClassComponent,
77 IncompleteFunctionComponent,
78 ScopeComponent,
79 OffscreenComponent,
80 LegacyHiddenComponent,
81 CacheComponent,
82 TracingMarkerComponent,
83 Throw,
84 ViewTransitionComponent,
85 ActivityComponent,
86 } from './ReactWorkTags';
87 import {
88 NoFlags,
89 PerformedWork,
90 Placement,
91 PlacementDEV,
92 Hydrating,
93 Callback,
94 ContentReset,
95 DidCapture,
96 Update,
97 Ref,
98 RefStatic,
99 ChildDeletion,
100 ForceUpdateForLegacySuspense,
101 StaticMask,
102 ShouldCapture,
103 ForceClientRender,
104 Passive,
105 DidDefer,
106 ViewTransitionNamedStatic,
107 ViewTransitionNamedMount,
108 LayoutStatic,
109 } from './ReactFiberFlags';
110 import {
111 disableLegacyContext,
112 disableLegacyContextForFunctionComponents,
113 enableProfilerCommitHooks,
114 enableProfilerTimer,
115 enableScopeAPI,
116 enableSchedulingProfiler,
117 enableTransitionTracing,
118 enableLegacyHidden,
119 enableCPUSuspense,
120 disableLegacyMode,
121 enableViewTransition,
122 enableFragmentRefs,
123 } from 'shared/ReactFeatureFlags';
124 import shallowEqual from 'shared/shallowEqual';
125 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
126 import getComponentNameFromType from 'shared/getComponentNameFromType';
127 import ReactStrictModeWarnings from './ReactStrictModeWarnings';
128 import {
129 REACT_LAZY_TYPE,
130 REACT_FORWARD_REF_TYPE,
131 REACT_MEMO_TYPE,
132 REACT_CONTEXT_TYPE,
133 } from 'shared/ReactSymbols';
134 import {REACT_RECOVERABLE_DIGEST} from 'shared/ReactRecoverable';
135 import {setCurrentFiber} from './ReactCurrentFiber';
136 import {resolveTypeForHotReloading} from './ReactFiberHotReloading';
137
138 import {
139 mountChildFibers,
140 reconcileChildFibers,
141 cloneChildFibers,
142 validateSuspenseListChildren,
143 } from './ReactChildFiber';
144 import {
145 processUpdateQueue,
146 cloneUpdateQueue,
147 initializeUpdateQueue,
148 enqueueCapturedUpdate,
149 suspendIfUpdateReadFromEntangledAsyncAction,
150 } from './ReactFiberClassUpdateQueue';
151 import {
152 NoLane,
153 NoLanes,
154 OffscreenLane,
155 DefaultLane,
156 SomeRetryLane,
157 includesSomeLane,
158 includesOnlyRetries,
159 laneToLanes,
160 removeLanes,
161 mergeLanes,
162 getBumpedLaneForHydration,
163 pickArbitraryLane,
164 } from './ReactFiberLane';
165 import {
166 ConcurrentMode,
167 NoMode,
168 ProfileMode,
169 StrictLegacyMode,
170 } from './ReactTypeOfMode';
171 import {
172 shouldSetTextContent,
173 isSuspenseInstancePending,
174 isSuspenseInstanceFallback,
175 getSuspenseInstanceFallbackErrorDetails,
176 supportsHydration,
177 supportsResources,
178 supportsSingletons,
179 isPrimaryRenderer,
180 getResource,
181 createHoistableInstance,
182 HostTransitionContext,
183 } from './ReactFiberConfig';
184 import type {ActivityInstance, SuspenseInstance} from './ReactFiberConfig';
185 import {shouldError, shouldSuspend} from './ReactFiberReconciler';
186 import {
187 pushHostContext,
188 pushHostContainer,
189 getRootHostContainer,
190 } from './ReactFiberHostContext';
191 import {
192 suspenseStackCursor,
193 pushSuspenseListContext,
194 ForceSuspenseFallback,
195 hasSuspenseListContext,
196 setDefaultShallowSuspenseListContext,
197 setShallowSuspenseListContext,
198 pushPrimaryTreeSuspenseHandler,
199 pushFallbackTreeSuspenseHandler,
200 pushDehydratedActivitySuspenseHandler,
201 pushOffscreenSuspenseHandler,
202 reuseSuspenseHandlerOnStack,
203 popSuspenseHandler,
204 } from './ReactFiberSuspenseContext';
205 import {
206 pushHiddenContext,
207 reuseHiddenContextOnStack,
208 isCurrentTreeHidden,
209 } from './ReactFiberHiddenContext';
210 import {findFirstSuspended} from './ReactFiberSuspenseComponent';
211 import {
212 pushProvider,
213 propagateContextChange,
214 lazilyPropagateParentContextChanges,
215 propagateParentContextChangesToDeferredTree,
216 checkIfContextChanged,
217 readContext,
218 prepareToReadContext,
219 scheduleContextWorkOnParentPath,
220 } from './ReactFiberNewContext';
221 import {
222 renderWithHooks,
223 checkDidRenderIdHook,
224 bailoutHooks,
225 replaySuspendedComponentWithHooks,
226 renderTransitionAwareHostComponentWithHooks,
227 } from './ReactFiberHooks';
228 import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
229 import {
230 getMaskedContext,
231 getUnmaskedContext,
232 hasContextChanged as hasLegacyContextChanged,
233 pushContextProvider as pushLegacyContextProvider,
234 isContextProvider as isLegacyContextProvider,
235 pushTopLevelContextObject,
236 invalidateContextProvider,
237 } from './ReactFiberLegacyContext';
238 import {
239 getIsHydrating,
240 enterHydrationState,
241 reenterHydrationStateFromDehydratedActivityInstance,
242 reenterHydrationStateFromDehydratedSuspenseInstance,
243 resetHydrationState,
244 claimHydratableSingleton,
245 tryToClaimNextHydratableInstance,
246 tryToClaimNextHydratableTextInstance,
247 claimNextHydratableActivityInstance,
248 claimNextHydratableSuspenseInstance,
249 warnIfHydrating,
250 queueHydrationError,
251 } from './ReactFiberHydrationContext';
252 import {
253 constructClassInstance,
254 mountClassInstance,
255 resumeMountClassInstance,
256 updateClassInstance,
257 resolveClassComponentProps,
258 } from './ReactFiberClassComponent';
259 import {
260 createFiberFromTypeAndProps,
261 createFiberFromFragment,
262 createFiberFromOffscreen,
263 createWorkInProgress,
264 isSimpleFunctionComponent,
265 isFunctionClassComponent,
266 } from './ReactFiber';
267 import {
268 scheduleUpdateOnFiber,
269 renderDidSuspendDelayIfPossible,
270 markSkippedUpdateLanes,
271 markRenderDerivedCause,
272 getWorkInProgressRoot,
273 peekDeferredLane,
274 } from './ReactFiberWorkLoop';
275 import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
276 import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent';
277 import {
278 createCapturedValueFromError,
279 createCapturedValueAtFiber,
280 } from './ReactCapturedValue';
281 import {OffscreenVisible} from './ReactFiberOffscreenComponent';
282 import {
283 createClassErrorUpdate,
284 initializeClassErrorUpdate,
285 } from './ReactFiberThrow';
286 import {
287 getForksAtLevel,
288 isForkedChild,
289 pushTreeId,
290 pushMaterializedTreeId,
291 } from './ReactFiberTreeContext';
292 import {
293 requestCacheFromPool,
294 pushRootTransition,
295 getSuspendedCache,
296 pushTransition,
297 getOffscreenDeferredCache,
298 getPendingTransitions,
299 } from './ReactFiberTransition';
300 import {
301 getMarkerInstances,
302 pushMarkerInstance,
303 pushRootMarkerInstance,
304 TransitionTracingMarker,
305 } from './ReactFiberTracingMarkerComponent';
306 import {callComponentInDEV, callRenderInDEV} from './ReactFiberCallUserSpace';
307 import {resolveLazy} from './ReactFiberThenable';
308
309 // A special exception that's used to unwind the stack when an update flows
310 // into a dehydrated boundary.
311 export const SelectiveHydrationException: mixed = new Error(
312 "This is not a real error. It's an implementation detail of React's " +
313 "selective hydration feature. If this leaks into userspace, it's a bug in " +
314 'React. Please file an issue.',
315 );
316
317 let didReceiveUpdate: boolean = false;
318
319 let didWarnAboutBadClass;
320 let didWarnAboutContextTypeOnFunctionComponent;
321 let didWarnAboutContextTypes;
322 let didWarnAboutGetDerivedStateOnFunctionComponent;
323 export let didWarnAboutReassigningProps: boolean;
324 let didWarnAboutRevealOrder;
325 let didWarnAboutTailOptions;
326 let didWarnAboutClassNameOnViewTransition;
327
328 if (__DEV__) {
329 didWarnAboutBadClass = {} as {[string]: boolean};
330 didWarnAboutContextTypeOnFunctionComponent = {} as {[string]: boolean};
331 didWarnAboutContextTypes = {} as {[string]: boolean};
332 didWarnAboutGetDerivedStateOnFunctionComponent = {} as {[string]: boolean};
333 didWarnAboutReassigningProps = false;
334 didWarnAboutRevealOrder = {} as {[string]: boolean};
335 didWarnAboutTailOptions = {} as {[string]: boolean};
336 didWarnAboutClassNameOnViewTransition = {} as {[string]: boolean};
337 }
338
339 export function reconcileChildren(
340 current: Fiber | null,
341 workInProgress: Fiber,
342 nextChildren: any,
343 renderLanes: Lanes,
344 ) {
345 if (current === null) {
346 // If this is a fresh new component that hasn't been rendered yet, we
347 // won't update its child set by applying minimal side-effects. Instead,
348 // we will add them all to the child before it gets rendered. That means
349 // we can optimize this reconciliation pass by not tracking side-effects.
350 workInProgress.child = mountChildFibers(
351 workInProgress,
352 null,
353 nextChildren,
354 renderLanes,
355 );
356 } else {
357 // If the current child is the same as the work in progress, it means that
358 // we haven't yet started any work on these children. Therefore, we use
359 // the clone algorithm to create a copy of all the current children.
360
361 // If we had any progressed work already, that is invalid at this point so
362 // let's throw it out.
363 workInProgress.child = reconcileChildFibers(
364 workInProgress,
365 current.child,
366 nextChildren,
367 renderLanes,
368 );
369 }
370 }
371
372 function forceUnmountCurrentAndReconcile(
373 current: Fiber,
374 workInProgress: Fiber,
375 nextChildren: any,
376 renderLanes: Lanes,
377 ) {
378 // This function is fork of reconcileChildren. It's used in cases where we
379 // want to reconcile without matching against the existing set. This has the
380 // effect of all current children being unmounted; even if the type and key
381 // are the same, the old child is unmounted and a new child is created.
382 //
383 // To do this, we're going to go through the reconcile algorithm twice. In
384 // the first pass, we schedule a deletion for all the current children by
385 // passing null.
386 workInProgress.child = reconcileChildFibers(
387 workInProgress,
388 current.child,
389 null,
390 renderLanes,
391 );
392 // In the second pass, we mount the new children. The trick here is that we
393 // pass null in place of where we usually pass the current child set. This has
394 // the effect of remounting all children regardless of whether their
395 // identities match.
396 workInProgress.child = reconcileChildFibers(
397 workInProgress,
398 null,
399 nextChildren,
400 renderLanes,
401 );
402 }
403
404 function updateForwardRef(
405 current: Fiber | null,
406 workInProgress: Fiber,
407 Component: any,
408 nextProps: any,
409 renderLanes: Lanes,
410 ) {
411 // TODO: current can be non-null here even if the component
412 // hasn't yet mounted. This happens after the first render suspends.
413 // We'll need to figure out if this is fine or can cause issues.
414 let render = Component.render;
415 if (__DEV__) {
416 const resolvedRender = resolveTypeForHotReloading(render);
417 if (resolvedRender !== render) {
418 render = resolvedRender;
419 if (current !== null) {
420 didReceiveUpdate = true;
421 }
422 }
423 }
424 const ref = workInProgress.ref;
425
426 let propsWithoutRef;
427 if ('ref' in nextProps) {
428 // `ref` is just a prop now, but `forwardRef` expects it to not appear in
429 // the props object. This used to happen in the JSX runtime, but now we do
430 // it here.
431 propsWithoutRef = {} as {[string]: any};
432 for (const key in nextProps) {
433 // Since `ref` should only appear in props via the JSX transform, we can
434 // assume that this is a plain object. So we don't need a
435 // hasOwnProperty check.
436 if (key !== 'ref') {
437 propsWithoutRef[key] = nextProps[key];
438 }
439 }
440 } else {
441 propsWithoutRef = nextProps;
442 }
443
444 // The rest is a fork of updateFunctionComponent
445 prepareToReadContext(workInProgress, renderLanes);
446 if (enableSchedulingProfiler) {
447 markComponentRenderStarted(workInProgress);
448 }
449
450 const nextChildren = renderWithHooks(
451 current,
452 workInProgress,
453 render,
454 propsWithoutRef,
455 ref,
456 renderLanes,
457 );
458 const hasId = checkDidRenderIdHook();
459
460 if (enableSchedulingProfiler) {
461 markComponentRenderStopped();
462 }
463
464 if (current !== null && !didReceiveUpdate) {
465 bailoutHooks(current, workInProgress, renderLanes);
466 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
467 }
468
469 if (getIsHydrating() && hasId) {
470 pushMaterializedTreeId(workInProgress);
471 }
472
473 // React DevTools reads this flag.
474 workInProgress.flags |= PerformedWork;
475 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
476 return workInProgress.child;
477 }
478
479 function updateMemoComponent(
480 current: Fiber | null,
481 workInProgress: Fiber,
482 Component: any,
483 nextProps: any,
484 renderLanes: Lanes,
485 ): null | Fiber {
486 if (current === null) {
487 const type = Component.type;
488 if (isSimpleFunctionComponent(type) && Component.compare === null) {
489 let resolvedType = type;
490 if (__DEV__) {
491 resolvedType = resolveTypeForHotReloading(type);
492 }
493 // If this is a plain function component without default props,
494 // and with only the default shallow comparison, we upgrade it
495 // to a SimpleMemoComponent to allow fast path updates.
496 workInProgress.tag = SimpleMemoComponent;
497 workInProgress.type = resolvedType;
498 if (__DEV__) {
499 validateFunctionComponentInDev(workInProgress, type);
500 }
501 return updateSimpleMemoComponent(
502 current,
503 workInProgress,
504 resolvedType,
505 nextProps,
506 renderLanes,
507 );
508 }
509 const child = createFiberFromTypeAndProps(
510 Component.type,
511 null,
512 nextProps,
513 workInProgress,
514 workInProgress.mode,
515 renderLanes,
516 );
517 child.ref = workInProgress.ref;
518 child.return = workInProgress;
519 workInProgress.child = child;
520 return child;
521 }
522 const currentChild = current.child as any as Fiber; // This is always exactly one child
523 const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
524 current,
525 renderLanes,
526 );
527 if (!hasScheduledUpdateOrContext) {
528 // This will be the props with resolved defaultProps,
529 // unlike current.memoizedProps which will be the unresolved ones.
530 const prevProps = currentChild.memoizedProps;
531 // Default to shallow comparison
532 let compare = Component.compare;
533 compare = compare !== null ? compare : shallowEqual;
534 if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
535 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
536 }
537 }
538 // React DevTools reads this flag.
539 workInProgress.flags |= PerformedWork;
540 const newChild = createWorkInProgress(currentChild, nextProps);
541 newChild.ref = workInProgress.ref;
542 newChild.return = workInProgress;
543 workInProgress.child = newChild;
544 return newChild;
545 }
546
547 function updateSimpleMemoComponent(
548 current: Fiber | null,
549 workInProgress: Fiber,
550 Component: any,
551 nextProps: any,
552 renderLanes: Lanes,
553 ): null | Fiber {
554 // TODO: current can be non-null here even if the component
555 // hasn't yet mounted. This happens when the inner render suspends.
556 // We'll need to figure out if this is fine or can cause issues.
557 if (current !== null) {
558 const prevProps = current.memoizedProps;
559 if (
560 shallowEqual(prevProps, nextProps) &&
561 current.ref === workInProgress.ref &&
562 // Prevent bailout if the implementation changed due to hot reload.
563 (__DEV__ ? workInProgress.type === current.type : true)
564 ) {
565 didReceiveUpdate = false;
566
567 // The props are shallowly equal. Reuse the previous props object, like we
568 // would during a normal fiber bailout.
569 //
570 // We don't have strong guarantees that the props object is referentially
571 // equal during updates where we can't bail out anyway — like if the props
572 // are shallowly equal, but there's a local state or context update in the
573 // same batch.
574 //
575 // However, as a principle, we should aim to make the behavior consistent
576 // across different ways of memoizing a component. For example, React.memo
577 // has a different internal Fiber layout if you pass a normal function
578 // component (SimpleMemoComponent) versus if you pass a different type
579 // like forwardRef (MemoComponent). But this is an implementation detail.
580 // Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
581 // affect whether the props object is reused during a bailout.
582 workInProgress.pendingProps = nextProps = prevProps;
583
584 if (!checkScheduledUpdateOrContext(current, renderLanes)) {
585 // The pending lanes were cleared at the beginning of beginWork. We're
586 // about to bail out, but there might be other lanes that weren't
587 // included in the current render. Usually, the priority level of the
588 // remaining updates is accumulated during the evaluation of the
589 // component (i.e. when processing the update queue). But since since
590 // we're bailing out early *without* evaluating the component, we need
591 // to account for it here, too. Reset to the value of the current fiber.
592 // NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
593 // because a MemoComponent fiber does not have hooks or an update queue;
594 // rather, it wraps around an inner component, which may or may not
595 // contains hooks.
596 // TODO: Move the reset at in beginWork out of the common path so that
597 // this is no longer necessary.
598 workInProgress.lanes = current.lanes;
599 return bailoutOnAlreadyFinishedWork(
600 current,
601 workInProgress,
602 renderLanes,
603 );
604 } else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
605 // This is a special case that only exists for legacy mode.
606 // See https://github.com/facebook/react/pull/19216.
607 didReceiveUpdate = true;
608 }
609 }
610 }
611 return updateFunctionComponent(
612 current,
613 workInProgress,
614 Component,
615 nextProps,
616 renderLanes,
617 );
618 }
619
620 function updateOffscreenComponent(
621 current: Fiber | null,
622 workInProgress: Fiber,
623 renderLanes: Lanes,
624 nextProps: OffscreenProps,
625 ) {
626 const nextChildren = nextProps.children;
627
628 const prevState: OffscreenState | null =
629 current !== null ? current.memoizedState : null;
630
631 if (current === null && workInProgress.stateNode === null) {
632 // We previously reset the work-in-progress.
633 // We need to create a new Offscreen instance.
634 const primaryChildInstance: OffscreenInstance = {
635 _visibility: OffscreenVisible,
636 _pendingMarkers: null,
637 _retryCache: null,
638 _transitions: null,
639 };
640 workInProgress.stateNode = primaryChildInstance;
641 }
642
643 if (
644 nextProps.mode === 'hidden' ||
645 (enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
646 ) {
647 // Rendering a hidden tree.
648
649 const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
650 if (didSuspend) {
651 // Something suspended inside a hidden tree
652
653 // Include the base lanes from the last render
654 const nextBaseLanes =
655 prevState !== null
656 ? mergeLanes(prevState.baseLanes, renderLanes)
657 : renderLanes;
658
659 let remainingChildLanes;
660 if (current !== null) {
661 // Reset to the current children
662 let currentChild = (workInProgress.child = current.child);
663
664 // The current render suspended, but there may be other lanes with
665 // pending work. We can't read `childLanes` from the current Offscreen
666 // fiber because we reset it when it was deferred; however, we can read
667 // the pending lanes from the child fibers.
668 let currentChildLanes: Lanes = NoLanes;
669 while (currentChild !== null) {
670 currentChildLanes = mergeLanes(
671 mergeLanes(currentChildLanes, currentChild.lanes),
672 currentChild.childLanes,
673 );
674 currentChild = currentChild.sibling;
675 }
676 const lanesWeJustAttempted = nextBaseLanes;
677 remainingChildLanes = removeLanes(
678 currentChildLanes,
679 lanesWeJustAttempted,
680 );
681 } else {
682 remainingChildLanes = NoLanes;
683 workInProgress.child = null;
684 }
685
686 return deferHiddenOffscreenComponent(
687 current,
688 workInProgress,
689 nextBaseLanes,
690 renderLanes,
691 remainingChildLanes,
692 );
693 }
694
695 if (
696 !disableLegacyMode &&
697 (workInProgress.mode & ConcurrentMode) === NoMode
698 ) {
699 // In legacy sync mode, don't defer the subtree. Render it now.
700 // TODO: Consider how Offscreen should work with transitions in the future
701 const nextState: OffscreenState = {
702 baseLanes: NoLanes,
703 cachePool: null,
704 };
705 workInProgress.memoizedState = nextState;
706 // push the cache pool even though we're going to bail out
707 // because otherwise there'd be a context mismatch
708 if (current !== null) {
709 pushTransition(workInProgress, null, null);
710 }
711 reuseHiddenContextOnStack(workInProgress);
712 pushOffscreenSuspenseHandler(workInProgress);
713 } else if (!includesSomeLane(renderLanes, OffscreenLane as Lane)) {
714 // We're hidden, and we're not rendering at Offscreen. We will bail out
715 // and resume this tree later.
716
717 // Schedule this fiber to re-render at Offscreen priority
718
719 const remainingChildLanes = (workInProgress.lanes =
720 laneToLanes(OffscreenLane));
721
722 // Include the base lanes from the last render
723 const nextBaseLanes =
724 prevState !== null
725 ? mergeLanes(prevState.baseLanes, renderLanes)
726 : renderLanes;
727
728 return deferHiddenOffscreenComponent(
729 current,
730 workInProgress,
731 nextBaseLanes,
732 renderLanes,
733 remainingChildLanes,
734 );
735 } else {
736 // This is the second render. The surrounding visible content has already
737 // committed. Now we resume rendering the hidden tree.
738
739 // Rendering at offscreen, so we can clear the base lanes.
740 const nextState: OffscreenState = {
741 baseLanes: NoLanes,
742 cachePool: null,
743 };
744 workInProgress.memoizedState = nextState;
745 if (current !== null) {
746 // If the render that spawned this one accessed the cache pool, resume
747 // using the same cache. Unless the parent changed, since that means
748 // there was a refresh.
749 const prevCachePool = prevState !== null ? prevState.cachePool : null;
750 // TODO: Consider if and how Offscreen pre-rendering should
751 // be attributed to the transition that spawned it
752 pushTransition(workInProgress, prevCachePool, null);
753 }
754
755 // Push the lanes that were skipped when we bailed out.
756 if (prevState !== null) {
757 pushHiddenContext(workInProgress, prevState);
758 } else {
759 reuseHiddenContextOnStack(workInProgress);
760 }
761 pushOffscreenSuspenseHandler(workInProgress);
762 }
763 } else {
764 // Rendering a visible tree.
765 if (prevState !== null) {
766 // We're going from hidden -> visible.
767 let prevCachePool = null;
768 // If the render that spawned this one accessed the cache pool, resume
769 // using the same cache. Unless the parent changed, since that means
770 // there was a refresh.
771 prevCachePool = prevState.cachePool;
772
773 let transitions = null;
774 if (enableTransitionTracing) {
775 // We have now gone from hidden to visible, so any transitions should
776 // be added to the stack to get added to any Offscreen/suspense children
777 const instance: OffscreenInstance | null = workInProgress.stateNode;
778 if (instance !== null && instance._transitions != null) {
779 transitions = Array.from(instance._transitions);
780 }
781 }
782
783 pushTransition(workInProgress, prevCachePool, transitions);
784
785 // Push the lanes that were skipped when we bailed out.
786 pushHiddenContext(workInProgress, prevState);
787 reuseSuspenseHandlerOnStack(workInProgress);
788
789 // Since we're not hidden anymore, reset the state
790 workInProgress.memoizedState = null;
791 } else {
792 // We weren't previously hidden, and we still aren't, so there's nothing
793 // special to do. Need to push to the stack regardless, though, to avoid
794 // a push/pop misalignment.
795
796 // If the render that spawned this one accessed the cache pool, resume
797 // using the same cache. Unless the parent changed, since that means
798 // there was a refresh.
799 if (current !== null) {
800 pushTransition(workInProgress, null, null);
801 }
802
803 // We're about to bail out, but we need to push this to the stack anyway
804 // to avoid a push/pop misalignment.
805 reuseHiddenContextOnStack(workInProgress);
806 reuseSuspenseHandlerOnStack(workInProgress);
807 }
808 }
809
810 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
811 return workInProgress.child;
812 }
813
814 function bailoutOffscreenComponent(
815 current: Fiber | null,
816 workInProgress: Fiber,
817 ): Fiber | null {
818 if (
819 (current === null || current.tag !== OffscreenComponent) &&
820 workInProgress.stateNode === null
821 ) {
822 const primaryChildInstance: OffscreenInstance = {
823 _visibility: OffscreenVisible,
824 _pendingMarkers: null,
825 _retryCache: null,
826 _transitions: null,
827 };
828 workInProgress.stateNode = primaryChildInstance;
829 }
830
831 return workInProgress.sibling;
832 }
833
834 function deferHiddenOffscreenComponent(
835 current: Fiber | null,
836 workInProgress: Fiber,
837 nextBaseLanes: Lanes,
838 renderLanes: Lanes,
839 remainingChildLanes: Lanes,
840 ) {
841 const nextState: OffscreenState = {
842 baseLanes: nextBaseLanes,
843 // Save the cache pool so we can resume later.
844 cachePool: getOffscreenDeferredCache(),
845 };
846 workInProgress.memoizedState = nextState;
847 // push the cache pool even though we're going to bail out
848 // because otherwise there'd be a context mismatch
849 if (current !== null) {
850 pushTransition(workInProgress, null, null);
851 }
852
853 // We're about to bail out, but we need to push this to the stack anyway
854 // to avoid a push/pop misalignment.
855 reuseHiddenContextOnStack(workInProgress);
856
857 pushOffscreenSuspenseHandler(workInProgress);
858
859 if (current !== null) {
860 // Since this tree will resume rendering in a separate render, we need
861 // to propagate parent contexts now so we don't lose track of which
862 // ones changed.
863 propagateParentContextChangesToDeferredTree(
864 current,
865 workInProgress,
866 renderLanes,
867 );
868 }
869
870 // We override the remaining child lanes to be the subset that we computed
871 // on the outside. We need to do this after propagating the context
872 // because propagateParentContextChangesToDeferredTree may schedule
873 // work which bubbles all the way up to the root and updates our child lanes.
874 // We want to dismiss that since we're not going to work on it yet.
875 workInProgress.childLanes = remainingChildLanes;
876
877 return null;
878 }
879
880 function updateLegacyHiddenComponent(
881 current: null | Fiber,
882 workInProgress: Fiber,
883 renderLanes: Lanes,
884 ) {
885 const nextProps: LegacyHiddenProps = workInProgress.pendingProps;
886 // Note: These happen to have identical begin phases, for now. We shouldn't hold
887 // ourselves to this constraint, though. If the behavior diverges, we should
888 // fork the function.
889 // This just works today because it has the same Props.
890 return updateOffscreenComponent(
891 current,
892 workInProgress,
893 renderLanes,
894 nextProps,
895 );
896 }
897
898 function mountActivityChildren(
899 workInProgress: Fiber,
900 nextProps: ActivityProps,
901 renderLanes: Lanes,
902 ) {
903 if (__DEV__) {
904 const hiddenProp = (nextProps as any).hidden;
905 if (hiddenProp !== undefined) {
906 console.error(
907 '<Activity> doesn\'t accept a hidden prop. Use mode="hidden" instead.\n' +
908 '- <Activity %s>\n' +
909 '+ <Activity %s>',
910 hiddenProp === true
911 ? 'hidden'
912 : hiddenProp === false
913 ? 'hidden={false}'
914 : 'hidden={...}',
915 hiddenProp ? 'mode="hidden"' : 'mode="visible"',
916 );
917 }
918 }
919 const nextChildren = nextProps.children;
920 const nextMode = nextProps.mode;
921 const mode = workInProgress.mode;
922 const offscreenChildProps: OffscreenProps = {
923 mode: nextMode,
924 children: nextChildren,
925 };
926 const primaryChildFragment = mountWorkInProgressOffscreenFiber(
927 offscreenChildProps,
928 mode,
929 renderLanes,
930 );
931 primaryChildFragment.ref = workInProgress.ref;
932 workInProgress.child = primaryChildFragment;
933 primaryChildFragment.return = workInProgress;
934 return primaryChildFragment;
935 }
936
937 function retryActivityComponentWithoutHydrating(
938 current: Fiber,
939 workInProgress: Fiber,
940 renderLanes: Lanes,
941 ) {
942 // Falling back to client rendering. Because this has performance
943 // implications, it's considered a recoverable error, even though the user
944 // likely won't observe anything wrong with the UI.
945
946 // This will add the old fiber to the deletion list
947 reconcileChildFibers(workInProgress, current.child, null, renderLanes);
948
949 // We're now not suspended nor dehydrated.
950 const nextProps: ActivityProps = workInProgress.pendingProps;
951 const primaryChildFragment = mountActivityChildren(
952 workInProgress,
953 nextProps,
954 renderLanes,
955 );
956 // Needs a placement effect because the parent (the Activity boundary) already
957 // mounted but this is a new fiber.
958 primaryChildFragment.flags |= Placement;
959
960 // If we're not going to hydrate we can't leave it dehydrated if something
961 // suspends. In that case we want that to bubble to the nearest parent boundary
962 // so we need to pop our own handler that we just pushed.
963 popSuspenseHandler(workInProgress);
964
965 workInProgress.memoizedState = null;
966
967 return primaryChildFragment;
968 }
969
970 function mountDehydratedActivityComponent(
971 workInProgress: Fiber,
972 activityInstance: ActivityInstance,
973 renderLanes: Lanes,
974 ): null | Fiber {
975 // During the first pass, we'll bail out and not drill into the children.
976 // Instead, we'll leave the content in place and try to hydrate it later.
977 // We'll continue hydrating the rest at offscreen priority since we'll already
978 // be showing the right content coming from the server, it is no rush.
979 workInProgress.lanes = laneToLanes(OffscreenLane);
980 return null;
981 }
982
983 function updateDehydratedActivityComponent(
984 current: Fiber,
985 workInProgress: Fiber,
986 didSuspend: boolean,
987 nextProps: ActivityProps,
988 activityInstance: ActivityInstance,
989 activityState: ActivityState,
990 renderLanes: Lanes,
991 ): null | Fiber {
992 // We'll handle suspending since if something suspends we can just leave
993 // it dehydrated. We push early and then pop if we enter non-dehydrated attempts.
994 pushDehydratedActivitySuspenseHandler(workInProgress);
995 if (!didSuspend) {
996 // This is the first render pass. Attempt to hydrate.
997
998 // We should never be hydrating at this point because it is the first pass,
999 // but after we've already committed once.
1000 warnIfHydrating();
1001
1002 if (includesSomeLane(renderLanes, OffscreenLane as Lane)) {
1003 // If we're rendering Offscreen and we're entering the activity then it's possible
1004 // that the only reason we rendered was because this boundary left work. Provide
1005 // it as a cause if another one doesn't already exist.
1006 markRenderDerivedCause(workInProgress);
1007 }
1008
1009 if (
1010 // TODO: Factoring is a little weird, since we check this right below, too.
1011 !didReceiveUpdate
1012 ) {
1013 // We need to check if any children have context before we decide to bail
1014 // out, so propagate the changes now.
1015 lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
1016 }
1017
1018 // We use lanes to indicate that a child might depend on context, so if
1019 // any context has changed, we need to treat is as if the input might have changed.
1020 const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
1021 if (didReceiveUpdate || hasContextChanged) {
1022 // This boundary has changed since the first render. This means that we are now unable to
1023 // hydrate it. We might still be able to hydrate it using a higher priority lane.
1024 if (isCurrentTreeHidden()) {
1025 // This boundary is inside a hidden subtree, where all work is
1026 // deferred until the tree is revealed. Selective hydration works by
1027 // rendering the boundary at a higher priority before the update
1028 // applies, so it can't make progress here; delaying the commit to
1029 // wait for it would deadlock. Replacing hidden content isn't
1030 // visible, so give up and client render.
1031 return retryActivityComponentWithoutHydrating(
1032 current,
1033 workInProgress,
1034 renderLanes,
1035 );
1036 }
1037 const root = getWorkInProgressRoot();
1038 if (root !== null) {
1039 const attemptHydrationAtLane = getBumpedLaneForHydration(
1040 root,
1041 renderLanes,
1042 );
1043 if (
1044 attemptHydrationAtLane !== NoLane &&
1045 attemptHydrationAtLane !== activityState.retryLane
1046 ) {
1047 // Intentionally mutating since this render will get interrupted. This
1048 // is one of the very rare times where we mutate the current tree
1049 // during the render phase.
1050 activityState.retryLane = attemptHydrationAtLane;
1051 enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
1052 scheduleUpdateOnFiber(root, current, attemptHydrationAtLane);
1053
1054 // Throw a special object that signals to the work loop that it should
1055 // interrupt the current render.
1056 //
1057 // Because we're inside a React-only execution stack, we don't
1058 // strictly need to throw here — we could instead modify some internal
1059 // work loop state. But using an exception means we don't need to
1060 // check for this case on every iteration of the work loop. So doing
1061 // it this way moves the check out of the fast path.
1062 throw SelectiveHydrationException;
1063 } else {
1064 // We have already tried to ping at a higher priority than we're rendering with
1065 // so if we got here, we must have failed to hydrate at those levels. We must
1066 // now give up. Instead, we're going to delete the whole subtree and instead inject
1067 // a new real Activity boundary to take its place. This might suspend for a while
1068 // and if it does we might still have an opportunity to hydrate before this pass
1069 // commits.
1070 }
1071 }
1072
1073 // If we did not selectively hydrate, we'll continue rendering without
1074 // hydrating. Mark this tree as suspended to prevent it from committing
1075 // outside a transition.
1076 //
1077 // This path should only happen if the hydration lane already suspended.
1078 renderDidSuspendDelayIfPossible();
1079 return retryActivityComponentWithoutHydrating(
1080 current,
1081 workInProgress,
1082 renderLanes,
1083 );
1084 } else {
1085 // This is the first attempt.
1086
1087 reenterHydrationStateFromDehydratedActivityInstance(
1088 workInProgress,
1089 activityInstance,
1090 activityState.treeContext,
1091 );
1092
1093 const primaryChildFragment = mountActivityChildren(
1094 workInProgress,
1095 nextProps,
1096 renderLanes,
1097 );
1098 // Mark the children as hydrating. This is a fast path to know whether this
1099 // tree is part of a hydrating tree. This is used to determine if a child
1100 // node has fully mounted yet, and for scheduling event replaying.
1101 // Conceptually this is similar to Placement in that a new subtree is
1102 // inserted into the React tree here. It just happens to not need DOM
1103 // mutations because it already exists.
1104 // We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
1105 primaryChildFragment.flags |= Hydrating | PlacementDEV;
1106 return primaryChildFragment;
1107 }
1108 } else {
1109 // This is the second render pass. We already attempted to hydrated, but
1110 // something either suspended or errored.
1111
1112 if (workInProgress.flags & ForceClientRender) {
1113 // Something errored during hydration. Try again without hydrating.
1114 // The error should've already been logged in throwException.
1115 workInProgress.flags &= ~ForceClientRender;
1116 return retryActivityComponentWithoutHydrating(
1117 current,
1118 workInProgress,
1119 renderLanes,
1120 );
1121 } else if (
1122 (workInProgress.memoizedState as null | ActivityState) !== null
1123 ) {
1124 // Something suspended and we should still be in dehydrated mode.
1125 // Leave the existing child in place.
1126
1127 workInProgress.child = current.child;
1128 // The dehydrated completion pass expects this flag to be there
1129 // but the normal offscreen pass doesn't.
1130 workInProgress.flags |= DidCapture;
1131 return null;
1132 } else {
1133 // We called retryActivityComponentWithoutHydrating and tried client rendering
1134 // but now we suspended again. We should never arrive here because we should
1135 // not have pushed a suspense handler during that second pass and it should
1136 // instead have suspended above.
1137 throw new Error(
1138 'Client rendering an Activity suspended it again. This is a bug in React.',
1139 );
1140 }
1141 }
1142 }
1143
1144 function updateActivityComponent(
1145 current: null | Fiber,
1146 workInProgress: Fiber,
1147 renderLanes: Lanes,
1148 ) {
1149 const nextProps: ActivityProps = workInProgress.pendingProps;
1150
1151 // Check if the first pass suspended.
1152 const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
1153 workInProgress.flags &= ~DidCapture;
1154
1155 if (current === null) {
1156 // Initial mount
1157
1158 // Special path for hydration
1159 // If we're currently hydrating, try to hydrate this boundary.
1160 // Hidden Activity boundaries are not emitted on the server.
1161 if (getIsHydrating()) {
1162 if (nextProps.mode === 'hidden') {
1163 // SSR doesn't render hidden Activity so it shouldn't hydrate,
1164 // even at offscreen lane. Defer to a client rendered offscreen lane.
1165 const primaryChildFragment = mountActivityChildren(
1166 workInProgress,
1167 nextProps,
1168 renderLanes,
1169 );
1170 workInProgress.lanes = laneToLanes(OffscreenLane);
1171 // This tree hasn't been mounted yet so there are no baseLanes to carry over.
1172 const nextState: OffscreenState = {
1173 baseLanes: NoLanes,
1174 cachePool: null,
1175 };
1176 primaryChildFragment.memoizedState = nextState;
1177
1178 return bailoutOffscreenComponent(null, primaryChildFragment);
1179 } else {
1180 // We must push the suspense handler context *before* attempting to
1181 // hydrate, to avoid a mismatch in case it errors.
1182 pushDehydratedActivitySuspenseHandler(workInProgress);
1183 const dehydrated: ActivityInstance =
1184 claimNextHydratableActivityInstance(workInProgress);
1185 return mountDehydratedActivityComponent(
1186 workInProgress,
1187 dehydrated,
1188 renderLanes,
1189 );
1190 }
1191 }
1192
1193 return mountActivityChildren(workInProgress, nextProps, renderLanes);
1194 } else {
1195 // This is an update.
1196
1197 // Special path for hydration
1198 const prevState: null | ActivityState = current.memoizedState;
1199
1200 if (prevState !== null) {
1201 const dehydrated = prevState.dehydrated;
1202 return updateDehydratedActivityComponent(
1203 current,
1204 workInProgress,
1205 didSuspend,
1206 nextProps,
1207 dehydrated,
1208 prevState,
1209 renderLanes,
1210 );
1211 }
1212
1213 const currentChild: Fiber = current.child as any;
1214
1215 const nextChildren = nextProps.children;
1216 const nextMode = nextProps.mode;
1217 const offscreenChildProps: OffscreenProps = {
1218 mode: nextMode,
1219 children: nextChildren,
1220 };
1221
1222 if (
1223 includesSomeLane(renderLanes, OffscreenLane as Lane) &&
1224 includesSomeLane(renderLanes, current.lanes)
1225 ) {
1226 // If we're rendering Offscreen and we're entering the activity then it's possible
1227 // that the only reason we rendered was because this boundary left work. Provide
1228 // it as a cause if another one doesn't already exist.
1229 markRenderDerivedCause(workInProgress);
1230 }
1231
1232 const primaryChildFragment = updateWorkInProgressOffscreenFiber(
1233 currentChild,
1234 offscreenChildProps,
1235 );
1236
1237 primaryChildFragment.ref = workInProgress.ref;
1238 workInProgress.child = primaryChildFragment;
1239 primaryChildFragment.return = workInProgress;
1240 return primaryChildFragment;
1241 }
1242 }
1243
1244 function updateCacheComponent(
1245 current: Fiber | null,
1246 workInProgress: Fiber,
1247 renderLanes: Lanes,
1248 ) {
1249 prepareToReadContext(workInProgress, renderLanes);
1250 const parentCache = readContext(CacheContext);
1251
1252 if (current === null) {
1253 // Initial mount. Request a fresh cache from the pool.
1254 const freshCache = requestCacheFromPool(renderLanes);
1255 const initialState: CacheComponentState = {
1256 parent: parentCache,
1257 cache: freshCache,
1258 };
1259 workInProgress.memoizedState = initialState;
1260 initializeUpdateQueue(workInProgress);
1261 pushCacheProvider(workInProgress, freshCache);
1262 } else {
1263 // Check for updates
1264 if (includesSomeLane(current.lanes, renderLanes)) {
1265 cloneUpdateQueue(current, workInProgress);
1266 processUpdateQueue(workInProgress, null, null, renderLanes);
1267 suspendIfUpdateReadFromEntangledAsyncAction();
1268 }
1269 const prevState: CacheComponentState = current.memoizedState;
1270 const nextState: CacheComponentState = workInProgress.memoizedState;
1271
1272 // Compare the new parent cache to the previous to see detect there was
1273 // a refresh.
1274 if (prevState.parent !== parentCache) {
1275 // Refresh in parent. Update the parent.
1276 const derivedState: CacheComponentState = {
1277 parent: parentCache,
1278 cache: parentCache,
1279 };
1280
1281 // Copied from getDerivedStateFromProps implementation. Once the update
1282 // queue is empty, persist the derived state onto the base state.
1283 workInProgress.memoizedState = derivedState;
1284 if (workInProgress.lanes === NoLanes) {
1285 const updateQueue: UpdateQueue<any> = workInProgress.updateQueue as any;
1286 workInProgress.memoizedState = updateQueue.baseState = derivedState;
1287 }
1288
1289 pushCacheProvider(workInProgress, parentCache);
1290 // No need to propagate a context change because the refreshed parent
1291 // already did.
1292 } else {
1293 // The parent didn't refresh. Now check if this cache did.
1294 const nextCache = nextState.cache;
1295 pushCacheProvider(workInProgress, nextCache);
1296 if (nextCache !== prevState.cache) {
1297 // This cache refreshed. Propagate a context change.
1298 propagateContextChange(workInProgress, CacheContext, renderLanes);
1299 }
1300 }
1301 }
1302
1303 const nextProps: CacheProps = workInProgress.pendingProps;
1304
1305 const nextChildren = nextProps.children;
1306 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1307 return workInProgress.child;
1308 }
1309
1310 // This should only be called if the name changes
1311 function updateTracingMarkerComponent(
1312 current: Fiber | null,
1313 workInProgress: Fiber,
1314 renderLanes: Lanes,
1315 ) {
1316 if (!enableTransitionTracing) {
1317 return null;
1318 }
1319
1320 const nextProps: TracingMarkerProps = workInProgress.pendingProps;
1321
1322 // TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
1323 // A tracing marker is only associated with the transitions that rendered
1324 // or updated it, so we can create a new set of transitions each time
1325 if (current === null) {
1326 const currentTransitions = getPendingTransitions();
1327 if (currentTransitions !== null) {
1328 const markerInstance: TracingMarkerInstance = {
1329 tag: TransitionTracingMarker,
1330 transitions: new Set(currentTransitions),
1331 pendingBoundaries: null,
1332 name: nextProps.name,
1333 aborts: null,
1334 };
1335 workInProgress.stateNode = markerInstance;
1336
1337 // We call the marker complete callback when all child suspense boundaries resolve.
1338 // We do this in the commit phase on Offscreen. If the marker has no child suspense
1339 // boundaries, we need to schedule a passive effect to make sure we call the marker
1340 // complete callback.
1341 workInProgress.flags |= Passive;
1342 }
1343 } else {
1344 if (__DEV__) {
1345 if (current.memoizedProps.name !== nextProps.name) {
1346 console.error(
1347 'Changing the name of a tracing marker after mount is not supported. ' +
1348 'To remount the tracing marker, pass it a new key.',
1349 );
1350 }
1351 }
1352 }
1353
1354 const instance: TracingMarkerInstance | null = workInProgress.stateNode;
1355 if (instance !== null) {
1356 pushMarkerInstance(workInProgress, instance);
1357 }
1358 const nextChildren = nextProps.children;
1359 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1360 return workInProgress.child;
1361 }
1362
1363 function updateFragment(
1364 current: Fiber | null,
1365 workInProgress: Fiber,
1366 renderLanes: Lanes,
1367 ) {
1368 const nextChildren = workInProgress.pendingProps;
1369 if (enableFragmentRefs) {
1370 markRef(current, workInProgress);
1371 }
1372 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1373 return workInProgress.child;
1374 }
1375
1376 function updateMode(
1377 current: Fiber | null,
1378 workInProgress: Fiber,
1379 renderLanes: Lanes,
1380 ) {
1381 const nextChildren = workInProgress.pendingProps.children;
1382 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1383 return workInProgress.child;
1384 }
1385
1386 function updateProfiler(
1387 current: Fiber | null,
1388 workInProgress: Fiber,
1389 renderLanes: Lanes,
1390 ) {
1391 if (enableProfilerTimer) {
1392 workInProgress.flags |= Update;
1393
1394 if (enableProfilerCommitHooks) {
1395 // Schedule a passive effect for this Profiler to call onPostCommit hooks.
1396 // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
1397 // because the effect is also where times bubble to parent Profilers.
1398 workInProgress.flags |= Passive;
1399 // Reset effect durations for the next eventual effect phase.
1400 // These are reset during render to allow the DevTools commit hook a chance to read them,
1401 const stateNode = workInProgress.stateNode;
1402 stateNode.effectDuration = -0;
1403 stateNode.passiveEffectDuration = -0;
1404 }
1405 }
1406 const nextProps: ProfilerProps = workInProgress.pendingProps;
1407 const nextChildren = nextProps.children;
1408 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1409 return workInProgress.child;
1410 }
1411
1412 function markRef(current: Fiber | null, workInProgress: Fiber) {
1413 // TODO: Check props.ref instead of fiber.ref when enableRefAsProp is on.
1414 const ref = workInProgress.ref;
1415 if (ref === null) {
1416 if (current !== null && current.ref !== null) {
1417 // Schedule a Ref effect
1418 workInProgress.flags |= Ref | RefStatic;
1419 }
1420 } else {
1421 if (typeof ref !== 'function' && typeof ref !== 'object') {
1422 throw new Error(
1423 'Expected ref to be a function, an object returned by React.createRef(), or undefined/null.',
1424 );
1425 }
1426 if (current === null || current.ref !== ref) {
1427 // Schedule a Ref effect
1428 workInProgress.flags |= Ref | RefStatic;
1429 }
1430 }
1431 }
1432
1433 function mountIncompleteFunctionComponent(
1434 _current: null | Fiber,
1435 workInProgress: Fiber,
1436 Component: any,
1437 nextProps: any,
1438 renderLanes: Lanes,
1439 ) {
1440 resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
1441
1442 workInProgress.tag = FunctionComponent;
1443
1444 return updateFunctionComponent(
1445 null,
1446 workInProgress,
1447 Component,
1448 nextProps,
1449 renderLanes,
1450 );
1451 }
1452
1453 function updateFunctionComponent(
1454 current: null | Fiber,
1455 workInProgress: Fiber,
1456 Component: any,
1457 nextProps: any,
1458 renderLanes: Lanes,
1459 ) {
1460 if (__DEV__) {
1461 if (
1462 Component.prototype &&
1463 typeof Component.prototype.render === 'function'
1464 ) {
1465 const componentName = getComponentNameFromType(Component) || 'Unknown';
1466
1467 if (!didWarnAboutBadClass[componentName]) {
1468 console.error(
1469 "The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
1470 'This is likely to cause errors. Change %s to extend React.Component instead.',
1471 componentName,
1472 componentName,
1473 );
1474 didWarnAboutBadClass[componentName] = true;
1475 }
1476 }
1477
1478 if (workInProgress.mode & StrictLegacyMode) {
1479 ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
1480 }
1481
1482 if (current === null) {
1483 // Some validations were previously done in mountIndeterminateComponent however and are now run
1484 // in updateFuntionComponent but only on mount
1485 validateFunctionComponentInDev(workInProgress, workInProgress.type);
1486
1487 if (Component.contextTypes) {
1488 const componentName = getComponentNameFromType(Component) || 'Unknown';
1489
1490 if (!didWarnAboutContextTypes[componentName]) {
1491 didWarnAboutContextTypes[componentName] = true;
1492 if (disableLegacyContext) {
1493 console.error(
1494 '%s uses the legacy contextTypes API which was removed in React 19. ' +
1495 'Use React.createContext() with React.useContext() instead. ' +
1496 '(https://react.dev/link/legacy-context)',
1497 componentName,
1498 );
1499 } else {
1500 console.error(
1501 '%s uses the legacy contextTypes API which will be removed soon. ' +
1502 'Use React.createContext() with React.useContext() instead. ' +
1503 '(https://react.dev/link/legacy-context)',
1504 componentName,
1505 );
1506 }
1507 }
1508 }
1509 }
1510 }
1511
1512 let context;
1513 if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
1514 const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
1515 context = getMaskedContext(workInProgress, unmaskedContext);
1516 }
1517
1518 let nextChildren;
1519 let hasId;
1520 prepareToReadContext(workInProgress, renderLanes);
1521 if (enableSchedulingProfiler) {
1522 markComponentRenderStarted(workInProgress);
1523 }
1524 if (__DEV__) {
1525 nextChildren = renderWithHooks(
1526 current,
1527 workInProgress,
1528 Component,
1529 nextProps,
1530 context,
1531 renderLanes,
1532 );
1533 hasId = checkDidRenderIdHook();
1534 } else {
1535 nextChildren = renderWithHooks(
1536 current,
1537 workInProgress,
1538 Component,
1539 nextProps,
1540 context,
1541 renderLanes,
1542 );
1543 hasId = checkDidRenderIdHook();
1544 }
1545 if (enableSchedulingProfiler) {
1546 markComponentRenderStopped();
1547 }
1548
1549 if (current !== null && !didReceiveUpdate) {
1550 bailoutHooks(current, workInProgress, renderLanes);
1551 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1552 }
1553
1554 if (getIsHydrating() && hasId) {
1555 pushMaterializedTreeId(workInProgress);
1556 }
1557
1558 // React DevTools reads this flag.
1559 workInProgress.flags |= PerformedWork;
1560 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1561 return workInProgress.child;
1562 }
1563
1564 export function replayFunctionComponent(
1565 current: Fiber | null,
1566 workInProgress: Fiber,
1567 nextProps: any,
1568 Component: any,
1569 secondArg: any,
1570 renderLanes: Lanes,
1571 ): Fiber | null {
1572 // This function is used to replay a component that previously suspended,
1573 // after its data resolves. It's a simplified version of
1574 // updateFunctionComponent that reuses the hooks from the previous attempt.
1575
1576 prepareToReadContext(workInProgress, renderLanes);
1577 if (enableSchedulingProfiler) {
1578 markComponentRenderStarted(workInProgress);
1579 }
1580 const nextChildren = replaySuspendedComponentWithHooks(
1581 current,
1582 workInProgress,
1583 Component,
1584 nextProps,
1585 secondArg,
1586 );
1587 const hasId = checkDidRenderIdHook();
1588 if (enableSchedulingProfiler) {
1589 markComponentRenderStopped();
1590 }
1591
1592 if (current !== null && !didReceiveUpdate) {
1593 bailoutHooks(current, workInProgress, renderLanes);
1594 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1595 }
1596
1597 if (getIsHydrating() && hasId) {
1598 pushMaterializedTreeId(workInProgress);
1599 }
1600
1601 // React DevTools reads this flag.
1602 workInProgress.flags |= PerformedWork;
1603 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1604 return workInProgress.child;
1605 }
1606
1607 function updateClassComponent(
1608 current: Fiber | null,
1609 workInProgress: Fiber,
1610 Component: any,
1611 nextProps: any,
1612 renderLanes: Lanes,
1613 ) {
1614 if (__DEV__) {
1615 // This is used by DevTools to force a boundary to error.
1616 switch (shouldError(workInProgress)) {
1617 case false: {
1618 // We previously simulated an error on this boundary
1619 // so the instance must have been constructed in a previous
1620 // commit.
1621 const instance = workInProgress.stateNode;
1622 const ctor = workInProgress.type;
1623 // TODO This way of resetting the error boundary state is a hack.
1624 // Is there a better way to do this?
1625 const tempInstance = new ctor(
1626 workInProgress.memoizedProps,
1627 instance.context,
1628 );
1629 const state = tempInstance.state;
1630 instance.updater.enqueueSetState(instance, state, null);
1631 break;
1632 }
1633 case true: {
1634 workInProgress.flags |= DidCapture;
1635 workInProgress.flags |= ShouldCapture;
1636 // eslint-disable-next-line react-internal/prod-error-codes
1637 const error = new Error('Simulated error coming from DevTools');
1638 const lane = pickArbitraryLane(renderLanes);
1639 workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
1640 // Schedule the error boundary to re-render using updated state
1641 const root: FiberRoot | null = getWorkInProgressRoot();
1642 if (root === null) {
1643 throw new Error(
1644 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1645 );
1646 }
1647 const update = createClassErrorUpdate(lane);
1648 initializeClassErrorUpdate(
1649 update,
1650 root,
1651 workInProgress,
1652 createCapturedValueAtFiber(error, workInProgress),
1653 );
1654 enqueueCapturedUpdate(workInProgress, update);
1655 break;
1656 }
1657 }
1658 }
1659
1660 // Push context providers early to prevent context stack mismatches.
1661 // During mounting we don't know the child context yet as the instance doesn't exist.
1662 // We will invalidate the child context in finishClassComponent() right after rendering.
1663 let hasContext;
1664 if (isLegacyContextProvider(Component)) {
1665 hasContext = true;
1666 pushLegacyContextProvider(workInProgress);
1667 } else {
1668 hasContext = false;
1669 }
1670 prepareToReadContext(workInProgress, renderLanes);
1671
1672 const instance = workInProgress.stateNode;
1673 let shouldUpdate;
1674 if (instance === null) {
1675 resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
1676
1677 // In the initial pass we might need to construct the instance.
1678 constructClassInstance(workInProgress, Component, nextProps);
1679 mountClassInstance(workInProgress, Component, nextProps, renderLanes);
1680 shouldUpdate = true;
1681 } else if (current === null) {
1682 // In a resume, we'll already have an instance we can reuse.
1683 shouldUpdate = resumeMountClassInstance(
1684 workInProgress,
1685 Component,
1686 nextProps,
1687 renderLanes,
1688 );
1689 } else {
1690 shouldUpdate = updateClassInstance(
1691 current,
1692 workInProgress,
1693 Component,
1694 nextProps,
1695 renderLanes,
1696 );
1697 }
1698 const nextUnitOfWork = finishClassComponent(
1699 current,
1700 workInProgress,
1701 Component,
1702 shouldUpdate,
1703 hasContext,
1704 renderLanes,
1705 );
1706 if (__DEV__) {
1707 const inst = workInProgress.stateNode;
1708 if (shouldUpdate && inst.props !== nextProps) {
1709 if (!didWarnAboutReassigningProps) {
1710 console.error(
1711 'It looks like %s is reassigning its own `this.props` while rendering. ' +
1712 'This is not supported and can lead to confusing bugs.',
1713 getComponentNameFromFiber(workInProgress) || 'a component',
1714 );
1715 }
1716 didWarnAboutReassigningProps = true;
1717 }
1718 }
1719 return nextUnitOfWork;
1720 }
1721
1722 function finishClassComponent(
1723 current: Fiber | null,
1724 workInProgress: Fiber,
1725 Component: any,
1726 shouldUpdate: boolean,
1727 hasContext: boolean,
1728 renderLanes: Lanes,
1729 ) {
1730 // Refs should update even if shouldComponentUpdate returns false
1731 markRef(current, workInProgress);
1732
1733 const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
1734
1735 if (!shouldUpdate && !didCaptureError) {
1736 // Context providers should defer to sCU for rendering
1737 if (hasContext) {
1738 invalidateContextProvider(workInProgress, Component, false);
1739 }
1740
1741 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1742 }
1743
1744 const instance = workInProgress.stateNode;
1745
1746 // Rerender
1747 if (__DEV__) {
1748 setCurrentFiber(workInProgress);
1749 }
1750 let nextChildren;
1751 if (
1752 didCaptureError &&
1753 typeof Component.getDerivedStateFromError !== 'function'
1754 ) {
1755 // If we captured an error, but getDerivedStateFromError is not defined,
1756 // unmount all the children. componentDidCatch will schedule an update to
1757 // re-render a fallback. This is temporary until we migrate everyone to
1758 // the new API.
1759 // TODO: Warn in a future release.
1760 nextChildren = null;
1761
1762 if (enableProfilerTimer) {
1763 stopProfilerTimerIfRunning(workInProgress);
1764 }
1765 } else {
1766 if (enableSchedulingProfiler) {
1767 markComponentRenderStarted(workInProgress);
1768 }
1769 if (__DEV__) {
1770 nextChildren = callRenderInDEV(instance);
1771 if (workInProgress.mode & StrictLegacyMode) {
1772 setIsStrictModeForDevtools(true);
1773 try {
1774 callRenderInDEV(instance);
1775 } finally {
1776 setIsStrictModeForDevtools(false);
1777 }
1778 }
1779 } else {
1780 nextChildren = instance.render();
1781 }
1782 if (enableSchedulingProfiler) {
1783 markComponentRenderStopped();
1784 }
1785 }
1786
1787 // React DevTools reads this flag.
1788 workInProgress.flags |= PerformedWork;
1789 if (current !== null && didCaptureError) {
1790 // If we're recovering from an error, reconcile without reusing any of
1791 // the existing children. Conceptually, the normal children and the children
1792 // that are shown on error are two different sets, so we shouldn't reuse
1793 // normal children even if their identities match.
1794 forceUnmountCurrentAndReconcile(
1795 current,
1796 workInProgress,
1797 nextChildren,
1798 renderLanes,
1799 );
1800 } else {
1801 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1802 }
1803
1804 // Memoize state using the values we just used to render.
1805 // TODO: Restructure so we never read values from the instance.
1806 workInProgress.memoizedState = instance.state;
1807
1808 // The context might have changed so we need to recalculate it.
1809 if (hasContext) {
1810 invalidateContextProvider(workInProgress, Component, true);
1811 }
1812
1813 return workInProgress.child;
1814 }
1815
1816 function pushHostRootContext(workInProgress: Fiber) {
1817 const root = workInProgress.stateNode as FiberRoot;
1818 if (root.pendingContext) {
1819 pushTopLevelContextObject(
1820 workInProgress,
1821 root.pendingContext,
1822 root.pendingContext !== root.context,
1823 );
1824 } else if (root.context) {
1825 // Should always be set
1826 pushTopLevelContextObject(workInProgress, root.context, false);
1827 }
1828 pushHostContainer(workInProgress, root.containerInfo);
1829 }
1830
1831 function updateHostRoot(
1832 current: null | Fiber,
1833 workInProgress: Fiber,
1834 renderLanes: Lanes,
1835 ) {
1836 pushHostRootContext(workInProgress);
1837
1838 if (current === null) {
1839 throw new Error('Should have a current fiber. This is a bug in React.');
1840 }
1841
1842 const nextProps = workInProgress.pendingProps;
1843 const prevState: RootState = workInProgress.memoizedState;
1844 const prevChildren = prevState.element;
1845 cloneUpdateQueue(current, workInProgress);
1846 processUpdateQueue(workInProgress, nextProps, null, renderLanes);
1847
1848 const nextState: RootState = workInProgress.memoizedState;
1849 const root: FiberRoot = workInProgress.stateNode;
1850 pushRootTransition(workInProgress, root, renderLanes);
1851
1852 if (enableTransitionTracing) {
1853 pushRootMarkerInstance(workInProgress);
1854 }
1855
1856 const nextCache: Cache = nextState.cache;
1857 pushCacheProvider(workInProgress, nextCache);
1858 if (nextCache !== prevState.cache) {
1859 // The root cache refreshed.
1860 propagateContextChange(workInProgress, CacheContext, renderLanes);
1861 }
1862
1863 // This would ideally go inside processUpdateQueue, but because it suspends,
1864 // it needs to happen after the `pushCacheProvider` call above to avoid a
1865 // context stack mismatch. A bit unfortunate.
1866 suspendIfUpdateReadFromEntangledAsyncAction();
1867
1868 // Caution: React DevTools currently depends on this property
1869 // being called "element".
1870 const nextChildren = nextState.element;
1871 // $FlowFixMe[constant-condition]
1872 if (supportsHydration && prevState.isDehydrated) {
1873 // This is a hydration root whose shell has not yet hydrated. We should
1874 // attempt to hydrate.
1875
1876 // Flip isDehydrated to false to indicate that when this render
1877 // finishes, the root will no longer be dehydrated.
1878 const overrideState: RootState = {
1879 element: nextChildren,
1880 isDehydrated: false,
1881 cache: nextState.cache,
1882 };
1883 const updateQueue: UpdateQueue<RootState> =
1884 workInProgress.updateQueue as any;
1885 // `baseState` can always be the last state because the root doesn't
1886 // have reducer functions so it doesn't need rebasing.
1887 updateQueue.baseState = overrideState;
1888 workInProgress.memoizedState = overrideState;
1889
1890 if (workInProgress.flags & ForceClientRender) {
1891 // Something errored during a previous attempt to hydrate the shell, so we
1892 // forced a client render. We should have a recoverable error already scheduled.
1893 return mountHostRootWithoutHydrating(
1894 current,
1895 workInProgress,
1896 nextChildren,
1897 renderLanes,
1898 );
1899 } else if (nextChildren !== prevChildren) {
1900 const recoverableError = createCapturedValueAtFiber<mixed>(
1901 new Error(
1902 'This root received an early update, before anything was able ' +
1903 'hydrate. Switched the entire root to client rendering.',
1904 ),
1905 workInProgress,
1906 );
1907 queueHydrationError(recoverableError);
1908 return mountHostRootWithoutHydrating(
1909 current,
1910 workInProgress,
1911 nextChildren,
1912 renderLanes,
1913 );
1914 } else {
1915 // The outermost shell has not hydrated yet. Start hydrating.
1916 enterHydrationState(workInProgress);
1917
1918 const child = mountChildFibers(
1919 workInProgress,
1920 null,
1921 nextChildren,
1922 renderLanes,
1923 );
1924 workInProgress.child = child;
1925
1926 let node = child;
1927 while (node) {
1928 // Mark each child as hydrating. This is a fast path to know whether this
1929 // tree is part of a hydrating tree. This is used to determine if a child
1930 // node has fully mounted yet, and for scheduling event replaying.
1931 // Conceptually this is similar to Placement in that a new subtree is
1932 // inserted into the React tree here. It just happens to not need DOM
1933 // mutations because it already exists.
1934 // We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
1935 node.flags = (node.flags & ~Placement) | Hydrating | PlacementDEV;
1936 node = node.sibling;
1937 }
1938 }
1939 } else {
1940 // Root is not dehydrated. Either this is a client-only root, or it
1941 // already hydrated.
1942 resetHydrationState();
1943 if (nextChildren === prevChildren) {
1944 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
1945 }
1946 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1947 }
1948 return workInProgress.child;
1949 }
1950
1951 function mountHostRootWithoutHydrating(
1952 current: Fiber,
1953 workInProgress: Fiber,
1954 nextChildren: ReactNodeList,
1955 renderLanes: Lanes,
1956 ) {
1957 // Revert to client rendering.
1958 resetHydrationState();
1959
1960 workInProgress.flags |= ForceClientRender;
1961
1962 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1963 return workInProgress.child;
1964 }
1965
1966 function updateHostComponent(
1967 current: Fiber | null,
1968 workInProgress: Fiber,
1969 renderLanes: Lanes,
1970 ) {
1971 if (current === null) {
1972 tryToClaimNextHydratableInstance(workInProgress);
1973 }
1974
1975 pushHostContext(workInProgress);
1976
1977 const type = workInProgress.type;
1978 const nextProps = workInProgress.pendingProps;
1979 const prevProps = current !== null ? current.memoizedProps : null;
1980
1981 let nextChildren = nextProps.children;
1982 const isDirectTextChild = shouldSetTextContent(type, nextProps);
1983
1984 if (isDirectTextChild) {
1985 // We special case a direct text child of a host node. This is a common
1986 // case. We won't handle it as a reified child. We will instead handle
1987 // this in the host environment that also has access to this prop. That
1988 // avoids allocating another HostText fiber and traversing it.
1989 nextChildren = null;
1990 } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
1991 // If we're switching from a direct text child to a normal child, or to
1992 // empty, we need to schedule the text content to be reset.
1993 workInProgress.flags |= ContentReset;
1994 }
1995
1996 const memoizedState = workInProgress.memoizedState;
1997 if (memoizedState !== null) {
1998 // This fiber has been upgraded to a stateful component. The only way
1999 // happens currently is for form actions. We use hooks to track the
2000 // pending and error state of the form.
2001 //
2002 // Once a fiber is upgraded to be stateful, it remains stateful for the
2003 // rest of its lifetime.
2004 const newState = renderTransitionAwareHostComponentWithHooks(
2005 current,
2006 workInProgress,
2007 renderLanes,
2008 );
2009
2010 // If the transition state changed, propagate the change to all the
2011 // descendents. We use Context as an implementation detail for this.
2012 //
2013 // We need to update it here because
2014 // pushHostContext gets called before we process the state hook, to avoid
2015 // a state mismatch in the event that something suspends.
2016 //
2017 // NOTE: This assumes that there cannot be nested transition providers,
2018 // because the only renderer that implements this feature is React DOM,
2019 // and forms cannot be nested. If we did support nested providers, then
2020 // we would need to push a context value even for host fibers that
2021 // haven't been upgraded yet.
2022 // $FlowFixMe[constant-condition]
2023 if (isPrimaryRenderer) {
2024 HostTransitionContext._currentValue = newState;
2025 } else {
2026 HostTransitionContext._currentValue2 = newState;
2027 }
2028 }
2029
2030 markRef(current, workInProgress);
2031 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
2032 return workInProgress.child;
2033 }
2034
2035 function updateHostHoistable(
2036 current: null | Fiber,
2037 workInProgress: Fiber,
2038 renderLanes: Lanes,
2039 ) {
2040 markRef(current, workInProgress);
2041
2042 if (current === null) {
2043 const resource = getResource(
2044 workInProgress.type,
2045 null,
2046 workInProgress.pendingProps,
2047 null,
2048 );
2049 if (resource) {
2050 workInProgress.memoizedState = resource;
2051 } else {
2052 if (!getIsHydrating()) {
2053 // This is not a Resource Hoistable and we aren't hydrating so we construct the instance.
2054 workInProgress.stateNode = createHoistableInstance(
2055 workInProgress.type,
2056 workInProgress.pendingProps,
2057 getRootHostContainer(),
2058 workInProgress,
2059 );
2060 }
2061 }
2062 } else {
2063 // Get Resource may or may not return a resource. either way we stash the result
2064 // on memoized state.
2065 workInProgress.memoizedState = getResource(
2066 workInProgress.type,
2067 current.memoizedProps,
2068 workInProgress.pendingProps,
2069 current.memoizedState,
2070 );
2071 }
2072
2073 // Resources never have reconciler managed children. It is possible for
2074 // the host implementation of getResource to consider children in the
2075 // resource construction but they will otherwise be discarded. In practice
2076 // this precludes all but the simplest children and Host specific warnings
2077 // should be implemented to warn when children are passsed when otherwise not
2078 // expected
2079 return null;
2080 }
2081
2082 function updateHostSingleton(
2083 current: Fiber | null,
2084 workInProgress: Fiber,
2085 renderLanes: Lanes,
2086 ) {
2087 pushHostContext(workInProgress);
2088
2089 if (current === null) {
2090 claimHydratableSingleton(workInProgress);
2091 }
2092
2093 const nextChildren = workInProgress.pendingProps.children;
2094 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
2095 markRef(current, workInProgress);
2096 if (current === null) {
2097 // We mark Singletons with a static flag to more efficiently manage their
2098 // ownership of the singleton host instance when in offscreen trees including Suspense
2099 workInProgress.flags |= LayoutStatic;
2100 }
2101 return workInProgress.child;
2102 }
2103
2104 function updateHostText(current: null | Fiber, workInProgress: Fiber) {
2105 if (current === null) {
2106 tryToClaimNextHydratableTextInstance(workInProgress);
2107 }
2108 // Nothing to do here. This is terminal. We'll do the completion step
2109 // immediately after.
2110 return null;
2111 }
2112
2113 function mountLazyComponent(
2114 _current: null | Fiber,
2115 workInProgress: Fiber,
2116 elementType: any,
2117 renderLanes: Lanes,
2118 ) {
2119 resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
2120
2121 const props = workInProgress.pendingProps;
2122 const lazyComponent: LazyComponentType<any, any> = elementType;
2123 let Component = resolveLazy(lazyComponent);
2124 if (__DEV__) {
2125 Component = resolveTypeForHotReloading(Component);
2126 }
2127 // Store the unwrapped component in the type.
2128 workInProgress.type = Component;
2129
2130 if (typeof Component === 'function') {
2131 if (isFunctionClassComponent(Component)) {
2132 const resolvedProps = resolveClassComponentProps(Component, props);
2133 workInProgress.tag = ClassComponent;
2134 return updateClassComponent(
2135 null,
2136 workInProgress,
2137 Component,
2138 resolvedProps,
2139 renderLanes,
2140 );
2141 } else {
2142 workInProgress.tag = FunctionComponent;
2143 if (__DEV__) {
2144 validateFunctionComponentInDev(workInProgress, Component);
2145 }
2146 return updateFunctionComponent(
2147 null,
2148 workInProgress,
2149 Component,
2150 props,
2151 renderLanes,
2152 );
2153 }
2154 // $FlowFixMe[invalid-compare]
2155 } else if (Component !== undefined && Component !== null) {
2156 const $$typeof = Component.$$typeof;
2157 // $FlowFixMe[invalid-compare]
2158 if ($$typeof === REACT_FORWARD_REF_TYPE) {
2159 workInProgress.tag = ForwardRef;
2160 return updateForwardRef(
2161 null,
2162 workInProgress,
2163 Component,
2164 props,
2165 renderLanes,
2166 );
2167 // $FlowFixMe[invalid-compare]
2168 } else if ($$typeof === REACT_MEMO_TYPE) {
2169 workInProgress.tag = MemoComponent;
2170 return updateMemoComponent(
2171 null,
2172 workInProgress,
2173 Component,
2174 props,
2175 renderLanes,
2176 );
2177 // $FlowFixMe[invalid-compare]
2178 } else if ($$typeof === REACT_CONTEXT_TYPE) {
2179 workInProgress.tag = ContextProvider;
2180 workInProgress.type = Component;
2181 return updateContextProvider(null, workInProgress, renderLanes);
2182 }
2183 }
2184
2185 let hint = '';
2186 if (__DEV__) {
2187 if (
2188 // $FlowFixMe[invalid-compare]
2189 Component !== null &&
2190 typeof Component === 'object' &&
2191 // $FlowFixMe[invalid-compare]
2192 Component.$$typeof === REACT_LAZY_TYPE
2193 ) {
2194 hint = ' Did you wrap a component in React.lazy() more than once?';
2195 }
2196 }
2197
2198 const loggedComponent = getComponentNameFromType(Component) || Component;
2199
2200 // This message intentionally doesn't mention ForwardRef or MemoComponent
2201 // because the fact that it's a separate type of work is an
2202 // implementation detail.
2203 throw new Error(
2204 `Element type is invalid. Received a promise that resolves to: ${loggedComponent}. ` +
2205 `Lazy element type must resolve to a class or function.${hint}`,
2206 );
2207 }
2208
2209 function mountIncompleteClassComponent(
2210 _current: null | Fiber,
2211 workInProgress: Fiber,
2212 Component: any,
2213 nextProps: any,
2214 renderLanes: Lanes,
2215 ) {
2216 resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
2217
2218 // Promote the fiber to a class and try rendering again.
2219 workInProgress.tag = ClassComponent;
2220
2221 // The rest of this function is a fork of `updateClassComponent`
2222
2223 // Push context providers early to prevent context stack mismatches.
2224 // During mounting we don't know the child context yet as the instance doesn't exist.
2225 // We will invalidate the child context in finishClassComponent() right after rendering.
2226 let hasContext;
2227 if (isLegacyContextProvider(Component)) {
2228 hasContext = true;
2229 pushLegacyContextProvider(workInProgress);
2230 } else {
2231 hasContext = false;
2232 }
2233 prepareToReadContext(workInProgress, renderLanes);
2234
2235 constructClassInstance(workInProgress, Component, nextProps);
2236 mountClassInstance(workInProgress, Component, nextProps, renderLanes);
2237
2238 return finishClassComponent(
2239 null,
2240 workInProgress,
2241 Component,
2242 true,
2243 hasContext,
2244 renderLanes,
2245 );
2246 }
2247
2248 function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
2249 if (__DEV__) {
2250 if (Component && Component.childContextTypes) {
2251 console.error(
2252 'childContextTypes cannot be defined on a function component.\n' +
2253 ' %s.childContextTypes = ...',
2254 Component.displayName || Component.name || 'Component',
2255 );
2256 }
2257
2258 if (typeof Component.getDerivedStateFromProps === 'function') {
2259 const componentName = getComponentNameFromType(Component) || 'Unknown';
2260
2261 if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
2262 console.error(
2263 '%s: Function components do not support getDerivedStateFromProps.',
2264 componentName,
2265 );
2266 didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
2267 }
2268 }
2269
2270 if (
2271 typeof Component.contextType === 'object' &&
2272 Component.contextType !== null
2273 ) {
2274 const componentName = getComponentNameFromType(Component) || 'Unknown';
2275
2276 if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
2277 console.error(
2278 '%s: Function components do not support contextType.',
2279 componentName,
2280 );
2281 didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
2282 }
2283 }
2284 }
2285 }
2286
2287 const SUSPENDED_MARKER: SuspenseState = {
2288 dehydrated: null,
2289 treeContext: null,
2290 retryLane: NoLane,
2291 hydrationErrors: null,
2292 };
2293
2294 function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
2295 return {
2296 baseLanes: renderLanes,
2297 cachePool: getSuspendedCache(),
2298 };
2299 }
2300
2301 function updateSuspenseOffscreenState(
2302 prevOffscreenState: OffscreenState,
2303 renderLanes: Lanes,
2304 ): OffscreenState {
2305 let cachePool: SpawnedCachePool | null = null;
2306 const prevCachePool: SpawnedCachePool | null = prevOffscreenState.cachePool;
2307 if (prevCachePool !== null) {
2308 // $FlowFixMe[constant-condition]
2309 const parentCache = isPrimaryRenderer
2310 ? CacheContext._currentValue
2311 : CacheContext._currentValue2;
2312 if (prevCachePool.parent !== parentCache) {
2313 // Detected a refresh in the parent. This overrides any previously
2314 // suspended cache.
2315 cachePool = {
2316 parent: parentCache,
2317 pool: parentCache,
2318 };
2319 } else {
2320 // We can reuse the cache from last time. The only thing that would have
2321 // overridden it is a parent refresh, which we checked for above.
2322 cachePool = prevCachePool;
2323 }
2324 } else {
2325 // If there's no previous cache pool, grab the current one.
2326 cachePool = getSuspendedCache();
2327 }
2328 return {
2329 baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
2330 cachePool,
2331 };
2332 }
2333
2334 // TODO: Probably should inline this back
2335 function shouldRemainOnFallback(
2336 current: null | Fiber,
2337 workInProgress: Fiber,
2338 renderLanes: Lanes,
2339 ) {
2340 // If we're already showing a fallback, there are cases where we need to
2341 // remain on that fallback regardless of whether the content has resolved.
2342 // For example, SuspenseList coordinates when nested content appears.
2343 // TODO: For compatibility with offscreen prerendering, this should also check
2344 // whether the current fiber (if it exists) was visible in the previous tree.
2345 if (current !== null) {
2346 const suspenseState: SuspenseState = current.memoizedState;
2347 // $FlowFixMe[invalid-compare]
2348 if (suspenseState === null) {
2349 // Currently showing content. Don't hide it, even if ForceSuspenseFallback
2350 // is true. More precise name might be "ForceRemainSuspenseFallback".
2351 // Note: This is a factoring smell. Can't remain on a fallback if there's
2352 // no fallback to remain on.
2353 return false;
2354 }
2355 }
2356
2357 // Not currently showing content. Consult the Suspense context.
2358 const suspenseContext: SuspenseContext = suspenseStackCursor.current;
2359 return hasSuspenseListContext(
2360 suspenseContext,
2361 ForceSuspenseFallback as SuspenseContext,
2362 );
2363 }
2364
2365 function getRemainingWorkInPrimaryTree(
2366 current: Fiber | null,
2367 primaryTreeDidDefer: boolean,
2368 renderLanes: Lanes,
2369 ) {
2370 let remainingLanes =
2371 current !== null ? removeLanes(current.childLanes, renderLanes) : NoLanes;
2372 if (primaryTreeDidDefer) {
2373 // A useDeferredValue hook spawned a deferred task inside the primary tree.
2374 // Ensure that we retry this component at the deferred priority.
2375 // TODO: We could make this a per-subtree value instead of a global one.
2376 // Would need to track it on the context stack somehow, similar to what
2377 // we'd have to do for resumable contexts.
2378 remainingLanes = mergeLanes(remainingLanes, peekDeferredLane());
2379 }
2380 return remainingLanes;
2381 }
2382
2383 function updateSuspenseComponent(
2384 current: null | Fiber,
2385 workInProgress: Fiber,
2386 renderLanes: Lanes,
2387 ) {
2388 const nextProps: SuspenseProps = workInProgress.pendingProps;
2389
2390 // This is used by DevTools to force a boundary to suspend.
2391 if (__DEV__) {
2392 if (shouldSuspend(workInProgress)) {
2393 workInProgress.flags |= DidCapture;
2394 }
2395 }
2396
2397 let showFallback = false;
2398 const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
2399 if (
2400 didSuspend ||
2401 shouldRemainOnFallback(current, workInProgress, renderLanes)
2402 ) {
2403 // Something in this boundary's subtree already suspended. Switch to
2404 // rendering the fallback children.
2405 showFallback = true;
2406 workInProgress.flags &= ~DidCapture;
2407 }
2408
2409 // Check if the primary children spawned a deferred task (useDeferredValue)
2410 // during the first pass.
2411 const didPrimaryChildrenDefer = (workInProgress.flags & DidDefer) !== NoFlags;
2412 workInProgress.flags &= ~DidDefer;
2413
2414 // OK, the next part is confusing. We're about to reconcile the Suspense
2415 // boundary's children. This involves some custom reconciliation logic. Two
2416 // main reasons this is so complicated.
2417 //
2418 // First, Legacy Mode has different semantics for backwards compatibility. The
2419 // primary tree will commit in an inconsistent state, so when we do the
2420 // second pass to render the fallback, we do some exceedingly, uh, clever
2421 // hacks to make that not totally break. Like transferring effects and
2422 // deletions from hidden tree. In Concurrent Mode, it's much simpler,
2423 // because we bailout on the primary tree completely and leave it in its old
2424 // state, no effects. Same as what we do for Offscreen (except that
2425 // Offscreen doesn't have the first render pass).
2426 //
2427 // Second is hydration. During hydration, the Suspense fiber has a slightly
2428 // different layout, where the child points to a dehydrated fragment, which
2429 // contains the DOM rendered by the server.
2430 //
2431 // Third, even if you set all that aside, Suspense is like error boundaries in
2432 // that we first we try to render one tree, and if that fails, we render again
2433 // and switch to a different tree. Like a try/catch block. So we have to track
2434 // which branch we're currently rendering. Ideally we would model this using
2435 // a stack.
2436 if (current === null) {
2437 // Initial mount
2438
2439 // Special path for hydration
2440 // If we're currently hydrating, try to hydrate this boundary.
2441 if (getIsHydrating()) {
2442 // We must push the suspense handler context *before* attempting to
2443 // hydrate, to avoid a mismatch in case it errors.
2444 if (showFallback) {
2445 pushPrimaryTreeSuspenseHandler(workInProgress);
2446 } else {
2447 pushFallbackTreeSuspenseHandler(workInProgress);
2448 }
2449 // This throws if we fail to hydrate.
2450 const dehydrated: SuspenseInstance =
2451 claimNextHydratableSuspenseInstance(workInProgress);
2452 return mountDehydratedSuspenseComponent(
2453 workInProgress,
2454 dehydrated,
2455 renderLanes,
2456 );
2457 }
2458
2459 const nextPrimaryChildren = nextProps.children;
2460 const nextFallbackChildren = nextProps.fallback;
2461
2462 if (showFallback) {
2463 pushFallbackTreeSuspenseHandler(workInProgress);
2464
2465 mountSuspenseFallbackChildren(
2466 workInProgress,
2467 nextPrimaryChildren,
2468 nextFallbackChildren,
2469 renderLanes,
2470 );
2471 const primaryChildFragment: Fiber = workInProgress.child as any;
2472 primaryChildFragment.memoizedState =
2473 mountSuspenseOffscreenState(renderLanes);
2474 primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
2475 current,
2476 didPrimaryChildrenDefer,
2477 renderLanes,
2478 );
2479 workInProgress.memoizedState = SUSPENDED_MARKER;
2480 if (enableTransitionTracing) {
2481 const currentTransitions = getPendingTransitions();
2482 if (currentTransitions !== null) {
2483 const parentMarkerInstances = getMarkerInstances();
2484 const offscreenQueue: OffscreenQueue | null =
2485 primaryChildFragment.updateQueue as any;
2486 if (offscreenQueue === null) {
2487 const newOffscreenQueue: OffscreenQueue = {
2488 transitions: currentTransitions,
2489 markerInstances: parentMarkerInstances,
2490 retryQueue: null,
2491 };
2492 primaryChildFragment.updateQueue = newOffscreenQueue;
2493 } else {
2494 offscreenQueue.transitions = currentTransitions;
2495 offscreenQueue.markerInstances = parentMarkerInstances;
2496 }
2497 }
2498 }
2499
2500 return bailoutOffscreenComponent(null, primaryChildFragment);
2501 } else if (enableCPUSuspense && nextProps.defer === true) {
2502 // This is a CPU-bound tree. Skip this tree and show a placeholder to
2503 // unblock the surrounding content. Then immediately retry after the
2504 // initial commit.
2505 pushFallbackTreeSuspenseHandler(workInProgress);
2506 mountSuspenseFallbackChildren(
2507 workInProgress,
2508 nextPrimaryChildren,
2509 nextFallbackChildren,
2510 renderLanes,
2511 );
2512 const primaryChildFragment: Fiber = workInProgress.child as any;
2513 primaryChildFragment.memoizedState =
2514 mountSuspenseOffscreenState(renderLanes);
2515 primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
2516 current,
2517 didPrimaryChildrenDefer,
2518 renderLanes,
2519 );
2520 workInProgress.memoizedState = SUSPENDED_MARKER;
2521
2522 // TODO: Transition Tracing is not yet implemented for CPU Suspense.
2523
2524 // Since nothing actually suspended, there will nothing to ping this to
2525 // get it started back up to attempt the next item. While in terms of
2526 // priority this work has the same priority as this current render, it's
2527 // not part of the same transition once the transition has committed. If
2528 // it's sync, we still want to yield so that it can be painted.
2529 // Conceptually, this is really the same as pinging. We can use any
2530 // RetryLane even if it's the one currently rendering since we're leaving
2531 // it behind on this node.
2532 workInProgress.lanes = SomeRetryLane;
2533 return bailoutOffscreenComponent(null, primaryChildFragment);
2534 } else {
2535 pushPrimaryTreeSuspenseHandler(workInProgress);
2536 return mountSuspensePrimaryChildren(
2537 workInProgress,
2538 nextPrimaryChildren,
2539 renderLanes,
2540 );
2541 }
2542 } else {
2543 // This is an update.
2544
2545 // Special path for hydration
2546 const prevState: null | SuspenseState = current.memoizedState;
2547 if (prevState !== null) {
2548 const dehydrated = prevState.dehydrated;
2549 if (dehydrated !== null) {
2550 return updateDehydratedSuspenseComponent(
2551 current,
2552 workInProgress,
2553 didSuspend,
2554 didPrimaryChildrenDefer,
2555 nextProps,
2556 dehydrated,
2557 prevState,
2558 renderLanes,
2559 );
2560 }
2561 }
2562
2563 if (showFallback) {
2564 pushFallbackTreeSuspenseHandler(workInProgress);
2565
2566 const nextFallbackChildren = nextProps.fallback;
2567 const nextPrimaryChildren = nextProps.children;
2568 updateSuspenseFallbackChildren(
2569 current,
2570 workInProgress,
2571 nextPrimaryChildren,
2572 nextFallbackChildren,
2573 renderLanes,
2574 );
2575 const primaryChildFragment: Fiber = workInProgress.child as any;
2576 const prevOffscreenState: OffscreenState | null = (current.child as any)
2577 .memoizedState;
2578 primaryChildFragment.memoizedState =
2579 prevOffscreenState === null
2580 ? mountSuspenseOffscreenState(renderLanes)
2581 : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
2582 if (enableTransitionTracing) {
2583 const currentTransitions = getPendingTransitions();
2584 if (currentTransitions !== null) {
2585 const parentMarkerInstances = getMarkerInstances();
2586 const offscreenQueue: OffscreenQueue | null =
2587 primaryChildFragment.updateQueue as any;
2588 const currentOffscreenQueue: OffscreenQueue | null =
2589 current.updateQueue as any;
2590 if (offscreenQueue === null) {
2591 const newOffscreenQueue: OffscreenQueue = {
2592 transitions: currentTransitions,
2593 markerInstances: parentMarkerInstances,
2594 retryQueue: null,
2595 };
2596 primaryChildFragment.updateQueue = newOffscreenQueue;
2597 } else if (offscreenQueue === currentOffscreenQueue) {
2598 // If the work-in-progress queue is the same object as current, we
2599 // can't modify it without cloning it first.
2600 const newOffscreenQueue: OffscreenQueue = {
2601 transitions: currentTransitions,
2602 markerInstances: parentMarkerInstances,
2603 retryQueue:
2604 currentOffscreenQueue !== null
2605 ? currentOffscreenQueue.retryQueue
2606 : null,
2607 };
2608 primaryChildFragment.updateQueue = newOffscreenQueue;
2609 } else {
2610 offscreenQueue.transitions = currentTransitions;
2611 offscreenQueue.markerInstances = parentMarkerInstances;
2612 }
2613 }
2614 }
2615 primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
2616 current,
2617 didPrimaryChildrenDefer,
2618 renderLanes,
2619 );
2620 workInProgress.memoizedState = SUSPENDED_MARKER;
2621 return bailoutOffscreenComponent(current.child, primaryChildFragment);
2622 } else {
2623 if (
2624 prevState !== null &&
2625 includesOnlyRetries(renderLanes) &&
2626 includesSomeLane(renderLanes, current.lanes)
2627 ) {
2628 // If we're rendering Retry lanes and we're entering the primary content then it's possible
2629 // that the only reason we rendered was because we left this boundary to be warmed up but
2630 // nothing else scheduled an update. If so, use it as the cause of the render.
2631 markRenderDerivedCause(workInProgress);
2632 }
2633
2634 pushPrimaryTreeSuspenseHandler(workInProgress);
2635
2636 const nextPrimaryChildren = nextProps.children;
2637 const primaryChildFragment = updateSuspensePrimaryChildren(
2638 current,
2639 workInProgress,
2640 nextPrimaryChildren,
2641 renderLanes,
2642 );
2643 workInProgress.memoizedState = null;
2644 return primaryChildFragment;
2645 }
2646 }
2647 }
2648
2649 function mountSuspensePrimaryChildren(
2650 workInProgress: Fiber,
2651 primaryChildren: $FlowFixMe,
2652 renderLanes: Lanes,
2653 ) {
2654 const mode = workInProgress.mode;
2655 const primaryChildProps: OffscreenProps = {
2656 mode: 'visible',
2657 children: primaryChildren,
2658 };
2659 const primaryChildFragment = mountWorkInProgressOffscreenFiber(
2660 primaryChildProps,
2661 mode,
2662 renderLanes,
2663 );
2664 primaryChildFragment.return = workInProgress;
2665 workInProgress.child = primaryChildFragment;
2666 return primaryChildFragment;
2667 }
2668
2669 function mountSuspenseFallbackChildren(
2670 workInProgress: Fiber,
2671 primaryChildren: $FlowFixMe,
2672 fallbackChildren: $FlowFixMe,
2673 renderLanes: Lanes,
2674 ) {
2675 const mode = workInProgress.mode;
2676 const progressedPrimaryFragment: Fiber | null = workInProgress.child;
2677
2678 const primaryChildProps: OffscreenProps = {
2679 mode: 'hidden',
2680 children: primaryChildren,
2681 };
2682
2683 let primaryChildFragment;
2684 let fallbackChildFragment;
2685 if (
2686 !disableLegacyMode &&
2687 (mode & ConcurrentMode) === NoMode &&
2688 progressedPrimaryFragment !== null
2689 ) {
2690 // In legacy mode, we commit the primary tree as if it successfully
2691 // completed, even though it's in an inconsistent state.
2692 primaryChildFragment = progressedPrimaryFragment;
2693 primaryChildFragment.childLanes = NoLanes;
2694 primaryChildFragment.pendingProps = primaryChildProps;
2695
2696 if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
2697 // Reset the durations from the first pass so they aren't included in the
2698 // final amounts. This seems counterintuitive, since we're intentionally
2699 // not measuring part of the render phase, but this makes it match what we
2700 // do in Concurrent Mode.
2701 primaryChildFragment.actualDuration = -0;
2702 primaryChildFragment.actualStartTime = -1.1;
2703 primaryChildFragment.selfBaseDuration = -0;
2704 primaryChildFragment.treeBaseDuration = -0;
2705 }
2706
2707 fallbackChildFragment = createFiberFromFragment(
2708 fallbackChildren,
2709 mode,
2710 renderLanes,
2711 null,
2712 );
2713 } else {
2714 primaryChildFragment = mountWorkInProgressOffscreenFiber(
2715 primaryChildProps,
2716 mode,
2717 NoLanes,
2718 );
2719 fallbackChildFragment = createFiberFromFragment(
2720 fallbackChildren,
2721 mode,
2722 renderLanes,
2723 null,
2724 );
2725 }
2726
2727 primaryChildFragment.return = workInProgress;
2728 fallbackChildFragment.return = workInProgress;
2729 primaryChildFragment.sibling = fallbackChildFragment;
2730 workInProgress.child = primaryChildFragment;
2731 return fallbackChildFragment;
2732 }
2733
2734 function mountWorkInProgressOffscreenFiber(
2735 offscreenProps: OffscreenProps,
2736 mode: TypeOfMode,
2737 renderLanes: Lanes,
2738 ) {
2739 // The props argument to `createFiberFromOffscreen` is `any` typed, so we use
2740 // this wrapper function to constrain it.
2741 return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null);
2742 }
2743
2744 function updateWorkInProgressOffscreenFiber(
2745 current: Fiber,
2746 offscreenProps: OffscreenProps,
2747 ) {
2748 // The props argument to `createWorkInProgress` is `any` typed, so we use this
2749 // wrapper function to constrain it.
2750 return createWorkInProgress(current, offscreenProps);
2751 }
2752
2753 function updateSuspensePrimaryChildren(
2754 current: Fiber,
2755 workInProgress: Fiber,
2756 primaryChildren: $FlowFixMe,
2757 renderLanes: Lanes,
2758 ) {
2759 const currentPrimaryChildFragment: Fiber = current.child as any;
2760 const currentFallbackChildFragment: Fiber | null =
2761 currentPrimaryChildFragment.sibling;
2762
2763 const primaryChildFragment = updateWorkInProgressOffscreenFiber(
2764 currentPrimaryChildFragment,
2765 {
2766 mode: 'visible',
2767 children: primaryChildren,
2768 },
2769 );
2770 if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
2771 primaryChildFragment.lanes = renderLanes;
2772 }
2773 primaryChildFragment.return = workInProgress;
2774 primaryChildFragment.sibling = null;
2775 if (currentFallbackChildFragment !== null) {
2776 // Delete the fallback child fragment
2777 const deletions = workInProgress.deletions;
2778 if (deletions === null) {
2779 workInProgress.deletions = [currentFallbackChildFragment];
2780 workInProgress.flags |= ChildDeletion;
2781 } else {
2782 deletions.push(currentFallbackChildFragment);
2783 }
2784 }
2785
2786 workInProgress.child = primaryChildFragment;
2787 return primaryChildFragment;
2788 }
2789
2790 function updateSuspenseFallbackChildren(
2791 current: Fiber,
2792 workInProgress: Fiber,
2793 primaryChildren: $FlowFixMe,
2794 fallbackChildren: $FlowFixMe,
2795 renderLanes: Lanes,
2796 ) {
2797 const mode = workInProgress.mode;
2798 const currentPrimaryChildFragment: Fiber = current.child as any;
2799 const currentFallbackChildFragment: Fiber | null =
2800 currentPrimaryChildFragment.sibling;
2801
2802 const primaryChildProps: OffscreenProps = {
2803 mode: 'hidden',
2804 children: primaryChildren,
2805 };
2806
2807 let primaryChildFragment;
2808 if (
2809 // In legacy mode, we commit the primary tree as if it successfully
2810 // completed, even though it's in an inconsistent state.
2811 !disableLegacyMode &&
2812 (mode & ConcurrentMode) === NoMode &&
2813 // Make sure we're on the second pass, i.e. the primary child fragment was
2814 // already cloned. In legacy mode, the only case where this isn't true is
2815 // when DevTools forces us to display a fallback; we skip the first render
2816 // pass entirely and go straight to rendering the fallback. (In Concurrent
2817 // Mode, SuspenseList can also trigger this scenario, but this is a legacy-
2818 // only codepath.)
2819 workInProgress.child !== currentPrimaryChildFragment
2820 ) {
2821 const progressedPrimaryFragment: Fiber = workInProgress.child as any;
2822 primaryChildFragment = progressedPrimaryFragment;
2823 primaryChildFragment.childLanes = NoLanes;
2824 primaryChildFragment.pendingProps = primaryChildProps;
2825
2826 if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
2827 // Reset the durations from the first pass so they aren't included in the
2828 // final amounts. This seems counterintuitive, since we're intentionally
2829 // not measuring part of the render phase, but this makes it match what we
2830 // do in Concurrent Mode.
2831 primaryChildFragment.actualDuration = -0;
2832 primaryChildFragment.actualStartTime = -1.1;
2833 primaryChildFragment.selfBaseDuration =
2834 currentPrimaryChildFragment.selfBaseDuration;
2835 primaryChildFragment.treeBaseDuration =
2836 currentPrimaryChildFragment.treeBaseDuration;
2837 }
2838
2839 // The fallback fiber was added as a deletion during the first pass.
2840 // However, since we're going to remain on the fallback, we no longer want
2841 // to delete it.
2842 workInProgress.deletions = null;
2843 } else {
2844 primaryChildFragment = updateWorkInProgressOffscreenFiber(
2845 currentPrimaryChildFragment,
2846 primaryChildProps,
2847 );
2848 // Since we're reusing a current tree, we need to reuse the flags, too.
2849 // (We don't do this in legacy mode, because in legacy mode we don't re-use
2850 // the current tree; see previous branch.)
2851 primaryChildFragment.subtreeFlags =
2852 currentPrimaryChildFragment.subtreeFlags & StaticMask;
2853 }
2854 let fallbackChildFragment;
2855 if (currentFallbackChildFragment !== null) {
2856 fallbackChildFragment = createWorkInProgress(
2857 currentFallbackChildFragment,
2858 fallbackChildren,
2859 );
2860 } else {
2861 fallbackChildFragment = createFiberFromFragment(
2862 fallbackChildren,
2863 mode,
2864 renderLanes,
2865 null,
2866 );
2867 // Needs a placement effect because the parent (the Suspense boundary) already
2868 // mounted but this is a new fiber.
2869 fallbackChildFragment.flags |= Placement;
2870 }
2871
2872 fallbackChildFragment.return = workInProgress;
2873 primaryChildFragment.return = workInProgress;
2874 primaryChildFragment.sibling = fallbackChildFragment;
2875 workInProgress.child = primaryChildFragment;
2876
2877 return bailoutOffscreenComponent(null, primaryChildFragment);
2878 }
2879
2880 function retrySuspenseComponentWithoutHydrating(
2881 current: Fiber,
2882 workInProgress: Fiber,
2883 renderLanes: Lanes,
2884 ) {
2885 // Falling back to client rendering. Because this has performance
2886 // implications, it's considered a recoverable error, even though the user
2887 // likely won't observe anything wrong with the UI.
2888
2889 // This will add the old fiber to the deletion list
2890 reconcileChildFibers(workInProgress, current.child, null, renderLanes);
2891
2892 // We're now not suspended nor dehydrated.
2893 const nextProps = workInProgress.pendingProps;
2894 const primaryChildren = nextProps.children;
2895 const primaryChildFragment = mountSuspensePrimaryChildren(
2896 workInProgress,
2897 primaryChildren,
2898 renderLanes,
2899 );
2900 // Needs a placement effect because the parent (the Suspense boundary) already
2901 // mounted but this is a new fiber.
2902 primaryChildFragment.flags |= Placement;
2903 workInProgress.memoizedState = null;
2904
2905 return primaryChildFragment;
2906 }
2907
2908 function mountSuspenseFallbackAfterRetryWithoutHydrating(
2909 current: Fiber,
2910 workInProgress: Fiber,
2911 primaryChildren: $FlowFixMe,
2912 fallbackChildren: $FlowFixMe,
2913 renderLanes: Lanes,
2914 ) {
2915 const fiberMode = workInProgress.mode;
2916 const primaryChildProps: OffscreenProps = {
2917 mode: 'visible',
2918 children: primaryChildren,
2919 };
2920 const primaryChildFragment = mountWorkInProgressOffscreenFiber(
2921 primaryChildProps,
2922 fiberMode,
2923 NoLanes,
2924 );
2925 const fallbackChildFragment = createFiberFromFragment(
2926 fallbackChildren,
2927 fiberMode,
2928 renderLanes,
2929 null,
2930 );
2931 // Needs a placement effect because the parent (the Suspense
2932 // boundary) already mounted but this is a new fiber.
2933 fallbackChildFragment.flags |= Placement;
2934
2935 primaryChildFragment.return = workInProgress;
2936 fallbackChildFragment.return = workInProgress;
2937 primaryChildFragment.sibling = fallbackChildFragment;
2938 workInProgress.child = primaryChildFragment;
2939
2940 if (disableLegacyMode || (workInProgress.mode & ConcurrentMode) !== NoMode) {
2941 // We will have dropped the effect list which contains the
2942 // deletion. We need to reconcile to delete the current child.
2943 reconcileChildFibers(workInProgress, current.child, null, renderLanes);
2944 }
2945
2946 return fallbackChildFragment;
2947 }
2948
2949 function mountDehydratedSuspenseComponent(
2950 workInProgress: Fiber,
2951 suspenseInstance: SuspenseInstance,
2952 renderLanes: Lanes,
2953 ): null | Fiber {
2954 // During the first pass, we'll bail out and not drill into the children.
2955 // Instead, we'll leave the content in place and try to hydrate it later.
2956 if (isSuspenseInstanceFallback(suspenseInstance)) {
2957 // This is a client-only boundary. Since we won't get any content from the server
2958 // for this, we need to schedule that at a higher priority based on when it would
2959 // have timed out. In theory we could render it in this pass but it would have the
2960 // wrong priority associated with it and will prevent hydration of parent path.
2961 // Instead, we'll leave work left on it to render it in a separate commit.
2962 // Schedule a normal pri update to render this content.
2963 workInProgress.lanes = laneToLanes(DefaultLane);
2964 } else {
2965 // We'll continue hydrating the rest at offscreen priority since we'll already
2966 // be showing the right content coming from the server, it is no rush.
2967 workInProgress.lanes = laneToLanes(OffscreenLane);
2968 }
2969 return null;
2970 }
2971
2972 function updateDehydratedSuspenseComponent(
2973 current: Fiber,
2974 workInProgress: Fiber,
2975 didSuspend: boolean,
2976 didPrimaryChildrenDefer: boolean,
2977 nextProps: SuspenseProps,
2978 suspenseInstance: SuspenseInstance,
2979 suspenseState: SuspenseState,
2980 renderLanes: Lanes,
2981 ): null | Fiber {
2982 if (!didSuspend) {
2983 // This is the first render pass. Attempt to hydrate.
2984 pushPrimaryTreeSuspenseHandler(workInProgress);
2985
2986 // We should never be hydrating at this point because it is the first pass,
2987 // but after we've already committed once.
2988 warnIfHydrating();
2989
2990 if (includesSomeLane(renderLanes, OffscreenLane as Lane)) {
2991 // If we're rendering Offscreen and we're entering the activity then it's possible
2992 // that the only reason we rendered was because this boundary left work. Provide
2993 // it as a cause if another one doesn't already exist.
2994 markRenderDerivedCause(workInProgress);
2995 }
2996
2997 if (isSuspenseInstanceFallback(suspenseInstance)) {
2998 // This boundary is in a permanent fallback state. In this case, we'll never
2999 // get an update and we'll never be able to hydrate the final content. Let's just try the
3000 // client side render instead.
3001 let digest: ?string;
3002 let message;
3003 let stack = null;
3004 let componentStack = null;
3005 if (__DEV__) {
3006 ({digest, message, stack, componentStack} =
3007 getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
3008 } else {
3009 ({digest} = getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
3010 }
3011
3012 // This is unreachable in renderers that do not support hydration.
3013 // $FlowFixMe[invalid-compare]
3014 if (digest !== REACT_RECOVERABLE_DIGEST) {
3015 let error: Error;
3016 if (__DEV__ && message) {
3017 // eslint-disable-next-line react-internal/prod-error-codes
3018 error = new Error(message);
3019 } else {
3020 error = new Error(
3021 'The server could not finish this Suspense boundary, likely ' +
3022 'due to an error during server rendering. ' +
3023 'Switched to client rendering.',
3024 );
3025 }
3026 // Replace the stack with the server stack
3027 error.stack = (__DEV__ && stack) || '';
3028 (error as any).digest = digest;
3029 const capturedValue = createCapturedValueFromError(
3030 error,
3031 componentStack === undefined ? null : componentStack,
3032 );
3033 queueHydrationError(capturedValue);
3034 }
3035 return retrySuspenseComponentWithoutHydrating(
3036 current,
3037 workInProgress,
3038 renderLanes,
3039 );
3040 }
3041
3042 if (
3043 // TODO: Factoring is a little weird, since we check this right below, too.
3044 !didReceiveUpdate
3045 ) {
3046 // We need to check if any children have context before we decide to bail
3047 // out, so propagate the changes now.
3048 lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
3049 }
3050
3051 // We use lanes to indicate that a child might depend on context, so if
3052 // any context has changed, we need to treat is as if the input might have changed.
3053 const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
3054 if (didReceiveUpdate || hasContextChanged) {
3055 // This boundary has changed since the first render. This means that we are now unable to
3056 // hydrate it. We might still be able to hydrate it using a higher priority lane.
3057 if (isCurrentTreeHidden()) {
3058 // This boundary is inside a hidden subtree, where all work is
3059 // deferred until the tree is revealed. Selective hydration works by
3060 // rendering the boundary at a higher priority before the update
3061 // applies, so it can't make progress here; delaying the commit to
3062 // wait for it would deadlock. Replacing hidden content isn't
3063 // visible, so give up and client render.
3064 return retrySuspenseComponentWithoutHydrating(
3065 current,
3066 workInProgress,
3067 renderLanes,
3068 );
3069 }
3070 const root = getWorkInProgressRoot();
3071 if (root !== null) {
3072 const attemptHydrationAtLane = getBumpedLaneForHydration(
3073 root,
3074 renderLanes,
3075 );
3076 if (
3077 attemptHydrationAtLane !== NoLane &&
3078 attemptHydrationAtLane !== suspenseState.retryLane
3079 ) {
3080 // Intentionally mutating since this render will get interrupted. This
3081 // is one of the very rare times where we mutate the current tree
3082 // during the render phase.
3083 suspenseState.retryLane = attemptHydrationAtLane;
3084 enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
3085 scheduleUpdateOnFiber(root, current, attemptHydrationAtLane);
3086
3087 // Throw a special object that signals to the work loop that it should
3088 // interrupt the current render.
3089 //
3090 // Because we're inside a React-only execution stack, we don't
3091 // strictly need to throw here — we could instead modify some internal
3092 // work loop state. But using an exception means we don't need to
3093 // check for this case on every iteration of the work loop. So doing
3094 // it this way moves the check out of the fast path.
3095 throw SelectiveHydrationException;
3096 } else {
3097 // We have already tried to ping at a higher priority than we're rendering with
3098 // so if we got here, we must have failed to hydrate at those levels. We must
3099 // now give up. Instead, we're going to delete the whole subtree and instead inject
3100 // a new real Suspense boundary to take its place, which may render content
3101 // or fallback. This might suspend for a while and if it does we might still have
3102 // an opportunity to hydrate before this pass commits.
3103 }
3104 }
3105
3106 // If we did not selectively hydrate, we'll continue rendering without
3107 // hydrating. Mark this tree as suspended to prevent it from committing
3108 // outside a transition.
3109 //
3110 // This path should only happen if the hydration lane already suspended.
3111 if (isSuspenseInstancePending(suspenseInstance)) {
3112 // This is a dehydrated suspense instance. We don't need to suspend
3113 // because we're already showing a fallback.
3114 // TODO: The Fizz runtime might still stream in completed HTML, out-of-
3115 // band. Should we fix this? There's a version of this bug that happens
3116 // during client rendering, too. Needs more consideration.
3117 } else {
3118 renderDidSuspendDelayIfPossible();
3119 }
3120 return retrySuspenseComponentWithoutHydrating(
3121 current,
3122 workInProgress,
3123 renderLanes,
3124 );
3125 } else if (isSuspenseInstancePending(suspenseInstance)) {
3126 // This component is still pending more data from the server, so we can't hydrate its
3127 // content. We treat it as if this component suspended itself. It might seem as if
3128 // we could just try to render it client-side instead. However, this will perform a
3129 // lot of unnecessary work and is unlikely to complete since it often will suspend
3130 // on missing data anyway. Additionally, the server might be able to render more
3131 // than we can on the client yet. In that case we'd end up with more fallback states
3132 // on the client than if we just leave it alone. If the server times out or errors
3133 // these should update this boundary to the permanent Fallback state instead.
3134 // Mark it as having captured (i.e. suspended).
3135 // Also Mark it as requiring retry.
3136 workInProgress.flags |= DidCapture | Callback;
3137 // Leave the child in place. I.e. the dehydrated fragment.
3138 workInProgress.child = current.child;
3139 return null;
3140 } else {
3141 // This is the first attempt.
3142 reenterHydrationStateFromDehydratedSuspenseInstance(
3143 workInProgress,
3144 suspenseInstance,
3145 suspenseState.treeContext,
3146 );
3147 const primaryChildren = nextProps.children;
3148 const primaryChildFragment = mountSuspensePrimaryChildren(
3149 workInProgress,
3150 primaryChildren,
3151 renderLanes,
3152 );
3153 // Mark the children as hydrating. This is a fast path to know whether this
3154 // tree is part of a hydrating tree. This is used to determine if a child
3155 // node has fully mounted yet, and for scheduling event replaying.
3156 // Conceptually this is similar to Placement in that a new subtree is
3157 // inserted into the React tree here. It just happens to not need DOM
3158 // mutations because it already exists.
3159 // We should still treat it as a newly inserted Fiber to double invoke Strict Effects.
3160 primaryChildFragment.flags |= Hydrating | PlacementDEV;
3161 return primaryChildFragment;
3162 }
3163 } else {
3164 // This is the second render pass. We already attempted to hydrated, but
3165 // something either suspended or errored.
3166
3167 if (workInProgress.flags & ForceClientRender) {
3168 // Something errored during hydration. Try again without hydrating.
3169 // The error should've already been logged in throwException.
3170 pushPrimaryTreeSuspenseHandler(workInProgress);
3171 workInProgress.flags &= ~ForceClientRender;
3172 return retrySuspenseComponentWithoutHydrating(
3173 current,
3174 workInProgress,
3175 renderLanes,
3176 );
3177 } else if (
3178 (workInProgress.memoizedState as null | SuspenseState) !== null
3179 ) {
3180 // Something suspended and we should still be in dehydrated mode.
3181 // Leave the existing child in place.
3182
3183 // Push to avoid a mismatch
3184 pushFallbackTreeSuspenseHandler(workInProgress);
3185
3186 workInProgress.child = current.child;
3187 // The dehydrated completion pass expects this flag to be there
3188 // but the normal suspense pass doesn't.
3189 workInProgress.flags |= DidCapture;
3190 return null;
3191 } else {
3192 // Suspended but we should no longer be in dehydrated mode.
3193 // Therefore we now have to render the fallback.
3194 pushFallbackTreeSuspenseHandler(workInProgress);
3195
3196 const nextPrimaryChildren = nextProps.children;
3197 const nextFallbackChildren = nextProps.fallback;
3198 mountSuspenseFallbackAfterRetryWithoutHydrating(
3199 current,
3200 workInProgress,
3201 nextPrimaryChildren,
3202 nextFallbackChildren,
3203 renderLanes,
3204 );
3205 const primaryChildFragment: Fiber = workInProgress.child as any;
3206 primaryChildFragment.memoizedState =
3207 mountSuspenseOffscreenState(renderLanes);
3208 primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
3209 current,
3210 didPrimaryChildrenDefer,
3211 renderLanes,
3212 );
3213 workInProgress.memoizedState = SUSPENDED_MARKER;
3214 return bailoutOffscreenComponent(null, primaryChildFragment);
3215 }
3216 }
3217 }
3218
3219 function scheduleSuspenseWorkOnFiber(
3220 fiber: Fiber,
3221 renderLanes: Lanes,
3222 propagationRoot: Fiber,
3223 ) {
3224 fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
3225 const alternate = fiber.alternate;
3226 if (alternate !== null) {
3227 alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
3228 }
3229 scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
3230 }
3231
3232 function propagateSuspenseContextChange(
3233 workInProgress: Fiber,
3234 firstChild: null | Fiber,
3235 renderLanes: Lanes,
3236 ): void {
3237 // Mark any Suspense boundaries with fallbacks as having work to do.
3238 // If they were previously forced into fallbacks, they may now be able
3239 // to unblock.
3240 let node = firstChild;
3241 while (node !== null) {
3242 if (node.tag === SuspenseComponent) {
3243 const state: SuspenseState | null = node.memoizedState;
3244 if (state !== null) {
3245 scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
3246 }
3247 } else if (node.tag === SuspenseListComponent) {
3248 // If the tail is hidden there might not be an Suspense boundaries
3249 // to schedule work on. In this case we have to schedule it on the
3250 // list itself.
3251 // We don't have to traverse to the children of the list since
3252 // the list will propagate the change when it rerenders.
3253 scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
3254 } else if (node.child !== null) {
3255 node.child.return = node;
3256 node = node.child;
3257 continue;
3258 }
3259 if (node === workInProgress) {
3260 return;
3261 }
3262 // $FlowFixMe[incompatible-use] found when upgrading Flow
3263 while (node.sibling === null) {
3264 // $FlowFixMe[incompatible-use] found when upgrading Flow
3265 if (node.return === null || node.return === workInProgress) {
3266 return;
3267 }
3268 node = node.return;
3269 }
3270 // $FlowFixMe[incompatible-use] found when upgrading Flow
3271 node.sibling.return = node.return;
3272 node = node.sibling;
3273 }
3274 }
3275
3276 function findLastContentRow(firstChild: null | Fiber): null | Fiber {
3277 // This is going to find the last row among these children that is already
3278 // showing content on the screen, as opposed to being in fallback state or
3279 // new. If a row has multiple Suspense boundaries, any of them being in the
3280 // fallback state, counts as the whole row being in a fallback state.
3281 // Note that the "rows" will be workInProgress, but any nested children
3282 // will still be current since we haven't rendered them yet. The mounted
3283 // order may not be the same as the new order. We use the new order.
3284 let row = firstChild;
3285 let lastContentRow: null | Fiber = null;
3286 while (row !== null) {
3287 const currentRow = row.alternate;
3288 // New rows can't be content rows.
3289 if (currentRow !== null && findFirstSuspended(currentRow) === null) {
3290 lastContentRow = row;
3291 }
3292 row = row.sibling;
3293 }
3294 return lastContentRow;
3295 }
3296
3297 function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
3298 if (__DEV__) {
3299 const cacheKey = revealOrder == null ? 'null' : revealOrder;
3300 if (
3301 revealOrder != null &&
3302 revealOrder !== 'forwards' &&
3303 revealOrder !== 'backwards' &&
3304 revealOrder !== 'unstable_legacy-backwards' &&
3305 revealOrder !== 'together' &&
3306 revealOrder !== 'independent' &&
3307 !didWarnAboutRevealOrder[cacheKey]
3308 ) {
3309 didWarnAboutRevealOrder[cacheKey] = true;
3310 if (typeof revealOrder === 'string') {
3311 switch (revealOrder.toLowerCase()) {
3312 // $FlowFixMe[invalid-compare]
3313 case 'together':
3314 // $FlowFixMe[invalid-compare] -- falls through
3315 case 'forwards':
3316 // $FlowFixMe[invalid-compare] -- falls through
3317 case 'backwards':
3318 // $FlowFixMe[invalid-compare] -- falls through
3319 case 'independent': {
3320 console.error(
3321 '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
3322 'Use lowercase "%s" instead.',
3323 revealOrder,
3324 revealOrder.toLowerCase(),
3325 );
3326 break;
3327 }
3328 // $FlowFixMe[invalid-compare]
3329 case 'forward':
3330 // $FlowFixMe[invalid-compare] -- falls through
3331 case 'backward': {
3332 console.error(
3333 '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
3334 'React uses the -s suffix in the spelling. Use "%ss" instead.',
3335 revealOrder,
3336 revealOrder.toLowerCase(),
3337 );
3338 break;
3339 }
3340 default:
3341 console.error(
3342 '"%s" is not a supported revealOrder on <SuspenseList />. ' +
3343 'Did you mean "independent", "together", "forwards" or "backwards"?',
3344 revealOrder,
3345 );
3346 break;
3347 }
3348 } else {
3349 console.error(
3350 '%s is not a supported value for revealOrder on <SuspenseList />. ' +
3351 'Did you mean "independent", "together", "forwards" or "backwards"?',
3352 revealOrder,
3353 );
3354 }
3355 }
3356 }
3357 }
3358
3359 function validateTailOptions(
3360 tailMode: SuspenseListTailMode,
3361 revealOrder: SuspenseListRevealOrder,
3362 ) {
3363 if (__DEV__) {
3364 const cacheKey = tailMode == null ? 'null' : tailMode;
3365 if (!didWarnAboutTailOptions[cacheKey]) {
3366 if (tailMode == null) {
3367 // The default tail is now "hidden".
3368 } else if (
3369 tailMode !== 'visible' &&
3370 tailMode !== 'collapsed' &&
3371 tailMode !== 'hidden'
3372 ) {
3373 didWarnAboutTailOptions[cacheKey] = true;
3374 console.error(
3375 '"%s" is not a supported value for tail on <SuspenseList />. ' +
3376 'Did you mean "visible", "collapsed" or "hidden"?',
3377 tailMode,
3378 );
3379 } else if (
3380 revealOrder != null &&
3381 revealOrder !== 'forwards' &&
3382 revealOrder !== 'backwards' &&
3383 revealOrder !== 'unstable_legacy-backwards'
3384 ) {
3385 didWarnAboutTailOptions[cacheKey] = true;
3386 console.error(
3387 '<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
3388 '"forwards" (default) or "backwards". ' +
3389 'Did you mean to specify revealOrder="forwards"?',
3390 tailMode,
3391 );
3392 }
3393 }
3394 }
3395 }
3396
3397 function initSuspenseListRenderState(
3398 workInProgress: Fiber,
3399 isBackwards: boolean,
3400 tail: null | Fiber,
3401 lastContentRow: null | Fiber,
3402 tailMode: SuspenseListTailMode,
3403 treeForkCount: number,
3404 ): void {
3405 const renderState: null | SuspenseListRenderState =
3406 workInProgress.memoizedState;
3407 if (renderState === null) {
3408 workInProgress.memoizedState = {
3409 isBackwards: isBackwards,
3410 rendering: null,
3411 renderingStartTime: 0,
3412 last: lastContentRow,
3413 tail: tail,
3414 tailMode: tailMode,
3415 treeForkCount: treeForkCount,
3416 } as SuspenseListRenderState;
3417 } else {
3418 // We can reuse the existing object from previous renders.
3419 renderState.isBackwards = isBackwards;
3420 renderState.rendering = null;
3421 renderState.renderingStartTime = 0;
3422 renderState.last = lastContentRow;
3423 renderState.tail = tail;
3424 renderState.tailMode = tailMode;
3425 renderState.treeForkCount = treeForkCount;
3426 }
3427 }
3428
3429 function reverseChildren(fiber: Fiber): void {
3430 let row = fiber.child;
3431 fiber.child = null;
3432 while (row !== null) {
3433 const nextRow = row.sibling;
3434 row.sibling = fiber.child;
3435 fiber.child = row;
3436 row = nextRow;
3437 }
3438 }
3439
3440 // This can end up rendering this component multiple passes.
3441 // The first pass splits the children fibers into two sets. A head and tail.
3442 // We first render the head. If anything is in fallback state, we do another
3443 // pass through beginWork to rerender all children (including the tail) with
3444 // the force suspend context. If the first render didn't have anything in
3445 // in fallback state. Then we render each row in the tail one-by-one.
3446 // That happens in the completeWork phase without going back to beginWork.
3447 function updateSuspenseListComponent(
3448 current: Fiber | null,
3449 workInProgress: Fiber,
3450 renderLanes: Lanes,
3451 ) {
3452 const nextProps: SuspenseListProps = workInProgress.pendingProps;
3453 const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
3454 const tailMode: SuspenseListTailMode = nextProps.tail;
3455 const newChildren = nextProps.children;
3456
3457 let suspenseContext: SuspenseContext = suspenseStackCursor.current;
3458
3459 if (workInProgress.flags & DidCapture) {
3460 // This is the second pass after having suspended in a row. Proceed directly
3461 // to the complete phase.
3462 pushSuspenseListContext(workInProgress, suspenseContext);
3463 return null;
3464 }
3465
3466 const shouldForceFallback = hasSuspenseListContext(
3467 suspenseContext,
3468 ForceSuspenseFallback as SuspenseContext,
3469 );
3470 if (shouldForceFallback) {
3471 suspenseContext = setShallowSuspenseListContext(
3472 suspenseContext,
3473 ForceSuspenseFallback,
3474 );
3475 workInProgress.flags |= DidCapture;
3476 } else {
3477 suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
3478 }
3479 pushSuspenseListContext(workInProgress, suspenseContext);
3480
3481 validateRevealOrder(revealOrder);
3482 validateTailOptions(tailMode, revealOrder);
3483 validateSuspenseListChildren(newChildren, revealOrder);
3484
3485 if (revealOrder === 'backwards' && current !== null) {
3486 // For backwards the current mounted set will be backwards. Reconciling against it
3487 // will lead to mismatches and reorders. We need to swap the original set first
3488 // and then restore it afterwards.
3489 reverseChildren(current);
3490 reconcileChildren(current, workInProgress, newChildren, renderLanes);
3491 reverseChildren(current);
3492 } else {
3493 reconcileChildren(current, workInProgress, newChildren, renderLanes);
3494 }
3495 // Read how many children forks this set pushed so we can push it every time we retry.
3496 const treeForkCount = getIsHydrating() ? getForksAtLevel(workInProgress) : 0;
3497
3498 if (!shouldForceFallback) {
3499 const didSuspendBefore =
3500 current !== null && (current.flags & DidCapture) !== NoFlags;
3501 if (didSuspendBefore) {
3502 // If we previously forced a fallback, we need to schedule work
3503 // on any nested boundaries to let them know to try to render
3504 // again. This is the same as context updating.
3505 propagateSuspenseContextChange(
3506 workInProgress,
3507 workInProgress.child,
3508 renderLanes,
3509 );
3510 }
3511 }
3512
3513 if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
3514 // In legacy mode, SuspenseList doesn't work so we just
3515 // use make it a noop by treating it as the default revealOrder.
3516 workInProgress.memoizedState = null;
3517 } else {
3518 switch (revealOrder) {
3519 case 'backwards': {
3520 // We're going to find the first row that has existing content.
3521 // We are also going to reverse the order of anything in the existing content
3522 // since we want to actually render them backwards from the reconciled set.
3523 // The tail is left in order, because it'll be added to the front as we
3524 // complete each item.
3525 const lastContentRow = findLastContentRow(workInProgress.child);
3526 let tail;
3527 if (lastContentRow === null) {
3528 // The whole list is part of the tail.
3529 tail = workInProgress.child;
3530 workInProgress.child = null;
3531 } else {
3532 // Disconnect the tail rows after the content row.
3533 // We're going to render them separately later in reverse order.
3534 tail = lastContentRow.sibling;
3535 lastContentRow.sibling = null;
3536 // We have to now reverse the main content so it renders backwards too.
3537 reverseChildren(workInProgress);
3538 }
3539 // TODO: If workInProgress.child is null, we can continue on the tail immediately.
3540 initSuspenseListRenderState(
3541 workInProgress,
3542 true, // isBackwards
3543 tail,
3544 null, // last
3545 tailMode,
3546 treeForkCount,
3547 );
3548 break;
3549 }
3550 case 'unstable_legacy-backwards': {
3551 // We're going to find the first row that has existing content.
3552 // At the same time we're going to reverse the list of everything
3553 // we pass in the meantime. That's going to be our tail in reverse
3554 // order.
3555 let tail = null;
3556 let row = workInProgress.child;
3557 workInProgress.child = null;
3558 while (row !== null) {
3559 const currentRow = row.alternate;
3560 // New rows can't be content rows.
3561 if (currentRow !== null && findFirstSuspended(currentRow) === null) {
3562 // This is the beginning of the main content.
3563 workInProgress.child = row;
3564 break;
3565 }
3566 const nextRow = row.sibling;
3567 row.sibling = tail;
3568 tail = row;
3569 row = nextRow;
3570 }
3571 // TODO: If workInProgress.child is null, we can continue on the tail immediately.
3572 initSuspenseListRenderState(
3573 workInProgress,
3574 true, // isBackwards
3575 tail,
3576 null, // last
3577 tailMode,
3578 treeForkCount,
3579 );
3580 break;
3581 }
3582 case 'together': {
3583 initSuspenseListRenderState(
3584 workInProgress,
3585 false, // isBackwards
3586 null, // tail
3587 null, // last
3588 undefined,
3589 treeForkCount,
3590 );
3591 break;
3592 }
3593 case 'independent': {
3594 // The "independent" reveal order is the same as not having
3595 // a boundary.
3596 workInProgress.memoizedState = null;
3597 break;
3598 }
3599 // The default is now forwards.
3600 case 'forwards':
3601 default: {
3602 const lastContentRow = findLastContentRow(workInProgress.child);
3603 let tail;
3604 if (lastContentRow === null) {
3605 // The whole list is part of the tail.
3606 // TODO: We could fast path by just rendering the tail now.
3607 tail = workInProgress.child;
3608 workInProgress.child = null;
3609 } else {
3610 // Disconnect the tail rows after the content row.
3611 // We're going to render them separately later.
3612 tail = lastContentRow.sibling;
3613 lastContentRow.sibling = null;
3614 }
3615 initSuspenseListRenderState(
3616 workInProgress,
3617 false, // isBackwards
3618 tail,
3619 lastContentRow,
3620 tailMode,
3621 treeForkCount,
3622 );
3623 break;
3624 }
3625 }
3626 }
3627 return workInProgress.child;
3628 }
3629
3630 function updateViewTransition(
3631 current: Fiber | null,
3632 workInProgress: Fiber,
3633 renderLanes: Lanes,
3634 ) {
3635 if (workInProgress.stateNode === null) {
3636 // We previously reset the work-in-progress.
3637 // We need to create a new ViewTransitionState instance.
3638 const instance: ViewTransitionState = {
3639 autoName: null,
3640 paired: null,
3641 clones: null,
3642 ref: null,
3643 };
3644 workInProgress.stateNode = instance;
3645 }
3646
3647 const pendingProps: ViewTransitionProps = workInProgress.pendingProps;
3648 if (pendingProps.name != null && pendingProps.name !== 'auto') {
3649 // Explicitly named boundary. We track it so that we can pair it up with another explicit
3650 // boundary if we get deleted.
3651 workInProgress.flags |=
3652 current === null
3653 ? ViewTransitionNamedMount | ViewTransitionNamedStatic
3654 : ViewTransitionNamedStatic;
3655 } else {
3656 // The server may have used useId to auto-assign a generated name for this boundary.
3657 // We push a materialization to ensure child ids line up with the server.
3658 if (getIsHydrating()) {
3659 pushMaterializedTreeId(workInProgress);
3660 }
3661 }
3662 if (__DEV__) {
3663 // $FlowFixMe[prop-missing]
3664 if (pendingProps.className !== undefined) {
3665 const example =
3666 typeof pendingProps.className === 'string'
3667 ? JSON.stringify(pendingProps.className)
3668 : '{...}';
3669 if (!didWarnAboutClassNameOnViewTransition[example]) {
3670 didWarnAboutClassNameOnViewTransition[example] = true;
3671 console.error(
3672 '<ViewTransition> doesn\'t accept a "className" prop. It has been renamed to "default".\n' +
3673 '- <ViewTransition className=%s>\n' +
3674 '+ <ViewTransition default=%s>',
3675 example,
3676 example,
3677 );
3678 }
3679 }
3680 }
3681 if (current !== null && current.memoizedProps.name !== pendingProps.name) {
3682 // If the name changes, we schedule a ref effect to create a new ref instance.
3683 workInProgress.flags |= Ref | RefStatic;
3684 } else {
3685 markRef(current, workInProgress);
3686 }
3687 const nextChildren = pendingProps.children;
3688 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
3689 return workInProgress.child;
3690 }
3691
3692 function updatePortalComponent(
3693 current: Fiber | null,
3694 workInProgress: Fiber,
3695 renderLanes: Lanes,
3696 ) {
3697 pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
3698 const nextChildren = workInProgress.pendingProps;
3699 if (current === null) {
3700 // Portals are special because we don't append the children during mount
3701 // but at commit. Therefore we need to track insertions which the normal
3702 // flow doesn't do during mount. This doesn't happen at the root because
3703 // the root always starts with a "current" with a null child.
3704 // TODO: Consider unifying this with how the root works.
3705 workInProgress.child = reconcileChildFibers(
3706 workInProgress,
3707 null,
3708 nextChildren,
3709 renderLanes,
3710 );
3711 } else {
3712 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
3713 }
3714 return workInProgress.child;
3715 }
3716
3717 let hasWarnedAboutUsingNoValuePropOnContextProvider = false;
3718
3719 function updateContextProvider(
3720 current: Fiber | null,
3721 workInProgress: Fiber,
3722 renderLanes: Lanes,
3723 ) {
3724 const context: ReactContext<any> = workInProgress.type;
3725 const newProps = workInProgress.pendingProps;
3726 const newValue = newProps.value;
3727
3728 if (__DEV__) {
3729 if (!('value' in newProps)) {
3730 if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
3731 hasWarnedAboutUsingNoValuePropOnContextProvider = true;
3732 console.error(
3733 'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
3734 );
3735 }
3736 }
3737 }
3738
3739 pushProvider(workInProgress, context, newValue);
3740
3741 const newChildren = newProps.children;
3742 reconcileChildren(current, workInProgress, newChildren, renderLanes);
3743 return workInProgress.child;
3744 }
3745
3746 function updateContextConsumer(
3747 current: Fiber | null,
3748 workInProgress: Fiber,
3749 renderLanes: Lanes,
3750 ) {
3751 const consumerType: ReactConsumerType<any> = workInProgress.type;
3752 const context: ReactContext<any> = consumerType._context;
3753 const newProps = workInProgress.pendingProps;
3754 const render = newProps.children;
3755
3756 if (__DEV__) {
3757 if (typeof render !== 'function') {
3758 console.error(
3759 'A context consumer was rendered with multiple children, or a child ' +
3760 "that isn't a function. A context consumer expects a single child " +
3761 'that is a function. If you did pass a function, make sure there ' +
3762 'is no trailing or leading whitespace around it.',
3763 );
3764 }
3765 }
3766
3767 prepareToReadContext(workInProgress, renderLanes);
3768 const newValue = readContext(context);
3769 if (enableSchedulingProfiler) {
3770 markComponentRenderStarted(workInProgress);
3771 }
3772 let newChildren;
3773 if (__DEV__) {
3774 newChildren = callComponentInDEV(render, newValue, undefined);
3775 } else {
3776 newChildren = render(newValue);
3777 }
3778 if (enableSchedulingProfiler) {
3779 markComponentRenderStopped();
3780 }
3781
3782 // React DevTools reads this flag.
3783 workInProgress.flags |= PerformedWork;
3784 reconcileChildren(current, workInProgress, newChildren, renderLanes);
3785 return workInProgress.child;
3786 }
3787
3788 function updateScopeComponent(
3789 current: null | Fiber,
3790 workInProgress: Fiber,
3791 renderLanes: Lanes,
3792 ) {
3793 const nextProps = workInProgress.pendingProps;
3794 const nextChildren = nextProps.children;
3795 markRef(current, workInProgress);
3796 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
3797 return workInProgress.child;
3798 }
3799
3800 export function markWorkInProgressReceivedUpdate() {
3801 didReceiveUpdate = true;
3802 }
3803
3804 export function checkIfWorkInProgressReceivedUpdate(): boolean {
3805 return didReceiveUpdate;
3806 }
3807
3808 function resetSuspendedCurrentOnMountInLegacyMode(
3809 current: null | Fiber,
3810 workInProgress: Fiber,
3811 ) {
3812 if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
3813 if (current !== null) {
3814 // A lazy component only mounts if it suspended inside a non-
3815 // concurrent tree, in an inconsistent state. We want to treat it like
3816 // a new mount, even though an empty version of it already committed.
3817 // Disconnect the alternate pointers.
3818 current.alternate = null;
3819 workInProgress.alternate = null;
3820 // Since this is conceptually a new fiber, schedule a Placement effect
3821 workInProgress.flags |= Placement;
3822 }
3823 }
3824 }
3825
3826 function bailoutOnAlreadyFinishedWork(
3827 current: Fiber | null,
3828 workInProgress: Fiber,
3829 renderLanes: Lanes,
3830 ): Fiber | null {
3831 if (current !== null) {
3832 // Reuse previous dependencies
3833 workInProgress.dependencies = current.dependencies;
3834 }
3835
3836 if (enableProfilerTimer) {
3837 // Don't update "base" render times for bailouts.
3838 stopProfilerTimerIfRunning(workInProgress);
3839 }
3840
3841 markSkippedUpdateLanes(workInProgress.lanes);
3842
3843 // Check if the children have any pending work.
3844 if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
3845 // The children don't have any work either. We can skip them.
3846 // TODO: Once we add back resuming, we should check if the children are
3847 // a work-in-progress set. If so, we need to transfer their effects.
3848
3849 if (current !== null) {
3850 // Before bailing out, check if there are any context changes in
3851 // the children.
3852 lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
3853 if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
3854 return null;
3855 }
3856 } else {
3857 return null;
3858 }
3859 }
3860
3861 // This fiber doesn't have work, but its subtree does. Clone the child
3862 // fibers and continue.
3863 cloneChildFibers(current, workInProgress);
3864 return workInProgress.child;
3865 }
3866
3867 function remountFiber(
3868 current: Fiber,
3869 oldWorkInProgress: Fiber,
3870 newWorkInProgress: Fiber,
3871 ): Fiber | null {
3872 if (__DEV__) {
3873 const returnFiber = oldWorkInProgress.return;
3874 if (returnFiber === null) {
3875 // eslint-disable-next-line react-internal/prod-error-codes
3876 throw new Error('Cannot swap the root fiber.');
3877 }
3878
3879 // Disconnect from the old current.
3880 // It will get deleted.
3881 current.alternate = null;
3882 oldWorkInProgress.alternate = null;
3883
3884 // Connect to the new tree.
3885 newWorkInProgress.index = oldWorkInProgress.index;
3886 newWorkInProgress.sibling = oldWorkInProgress.sibling;
3887 newWorkInProgress.return = oldWorkInProgress.return;
3888 newWorkInProgress.ref = oldWorkInProgress.ref;
3889
3890 // $FlowFixMe[constant-condition]
3891 if (__DEV__) {
3892 newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo;
3893 }
3894
3895 // Replace the child/sibling pointers above it.
3896 if (oldWorkInProgress === returnFiber.child) {
3897 returnFiber.child = newWorkInProgress;
3898 } else {
3899 let prevSibling = returnFiber.child;
3900 if (prevSibling === null) {
3901 // eslint-disable-next-line react-internal/prod-error-codes
3902 throw new Error('Expected parent to have a child.');
3903 }
3904 // $FlowFixMe[incompatible-use] found when upgrading Flow
3905 while (prevSibling.sibling !== oldWorkInProgress) {
3906 // $FlowFixMe[incompatible-use] found when upgrading Flow
3907 prevSibling = prevSibling.sibling;
3908 if (prevSibling === null) {
3909 // eslint-disable-next-line react-internal/prod-error-codes
3910 throw new Error('Expected to find the previous sibling.');
3911 }
3912 }
3913 // $FlowFixMe[incompatible-use] found when upgrading Flow
3914 prevSibling.sibling = newWorkInProgress;
3915 }
3916
3917 // Delete the old fiber and place the new one.
3918 // Since the old fiber is disconnected, we have to schedule it manually.
3919 const deletions = returnFiber.deletions;
3920 if (deletions === null) {
3921 returnFiber.deletions = [current];
3922 returnFiber.flags |= ChildDeletion;
3923 } else {
3924 deletions.push(current);
3925 }
3926
3927 newWorkInProgress.flags |= Placement | PlacementDEV;
3928
3929 // Restart work from the new fiber.
3930 return newWorkInProgress;
3931 } else {
3932 throw new Error(
3933 'Did not expect this call in production. ' +
3934 'This is a bug in React. Please file an issue.',
3935 );
3936 }
3937 }
3938
3939 function checkScheduledUpdateOrContext(
3940 current: Fiber,
3941 renderLanes: Lanes,
3942 ): boolean {
3943 // Before performing an early bailout, we must check if there are pending
3944 // updates or context.
3945 const updateLanes = current.lanes;
3946 if (includesSomeLane(updateLanes, renderLanes)) {
3947 return true;
3948 }
3949 // No pending update, but because context is propagated lazily, we need
3950 // to check for a context change before we bail out.
3951 const dependencies = current.dependencies;
3952 if (dependencies !== null && checkIfContextChanged(dependencies)) {
3953 return true;
3954 }
3955 return false;
3956 }
3957
3958 function attemptEarlyBailoutIfNoScheduledUpdate(
3959 current: Fiber,
3960 workInProgress: Fiber,
3961 renderLanes: Lanes,
3962 ) {
3963 // This fiber does not have any pending work. Bailout without entering
3964 // the begin phase. There's still some bookkeeping we that needs to be done
3965 // in this optimized path, mostly pushing stuff onto the stack.
3966 switch (workInProgress.tag) {
3967 case HostRoot: {
3968 pushHostRootContext(workInProgress);
3969 const root: FiberRoot = workInProgress.stateNode;
3970 pushRootTransition(workInProgress, root, renderLanes);
3971
3972 if (enableTransitionTracing) {
3973 pushRootMarkerInstance(workInProgress);
3974 }
3975
3976 const cache: Cache = current.memoizedState.cache;
3977 pushCacheProvider(workInProgress, cache);
3978 resetHydrationState();
3979 break;
3980 }
3981 case HostSingleton:
3982 case HostComponent:
3983 pushHostContext(workInProgress);
3984 break;
3985 case ClassComponent: {
3986 const Component = workInProgress.type;
3987 if (isLegacyContextProvider(Component)) {
3988 pushLegacyContextProvider(workInProgress);
3989 }
3990 break;
3991 }
3992 case HostPortal:
3993 pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
3994 break;
3995 case ContextProvider: {
3996 const newValue = workInProgress.memoizedProps.value;
3997 const context: ReactContext<any> = workInProgress.type;
3998 pushProvider(workInProgress, context, newValue);
3999 break;
4000 }
4001 case Profiler:
4002 if (enableProfilerTimer) {
4003 // Profiler should only call onRender when one of its descendants actually rendered.
4004 const hasChildWork = includesSomeLane(
4005 renderLanes,
4006 workInProgress.childLanes,
4007 );
4008 if (hasChildWork) {
4009 workInProgress.flags |= Update;
4010 }
4011
4012 if (enableProfilerCommitHooks) {
4013 // Schedule a passive effect for this Profiler to call onPostCommit hooks.
4014 // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
4015 // because the effect is also where times bubble to parent Profilers.
4016 workInProgress.flags |= Passive;
4017 // Reset effect durations for the next eventual effect phase.
4018 // These are reset during render to allow the DevTools commit hook a chance to read them,
4019 const stateNode = workInProgress.stateNode;
4020 stateNode.effectDuration = -0;
4021 stateNode.passiveEffectDuration = -0;
4022 }
4023 }
4024 break;
4025 case ActivityComponent: {
4026 const state: ActivityState | null = workInProgress.memoizedState;
4027 if (state !== null) {
4028 // We're dehydrated so we're not going to render the children. This is just
4029 // to maintain push/pop symmetry.
4030 // We know that this component will suspend again because if it has
4031 // been unsuspended it has committed as a hydrated Activity component.
4032 // If it needs to be retried, it should have work scheduled on it.
4033 workInProgress.flags |= DidCapture;
4034 pushDehydratedActivitySuspenseHandler(workInProgress);
4035 return null;
4036 }
4037 break;
4038 }
4039 case SuspenseComponent: {
4040 const state: SuspenseState | null = workInProgress.memoizedState;
4041 if (state !== null) {
4042 if (state.dehydrated !== null) {
4043 // We're not going to render the children, so this is just to maintain
4044 // push/pop symmetry
4045 pushPrimaryTreeSuspenseHandler(workInProgress);
4046 // We know that this component will suspend again because if it has
4047 // been unsuspended it has committed as a resolved Suspense component.
4048 // If it needs to be retried, it should have work scheduled on it.
4049 workInProgress.flags |= DidCapture;
4050 // We should never render the children of a dehydrated boundary until we
4051 // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
4052 return null;
4053 }
4054
4055 // If this boundary is currently timed out, we need to decide
4056 // whether to retry the primary children, or to skip over it and
4057 // go straight to the fallback. Check the priority of the primary
4058 // child fragment.
4059 //
4060 // Propagate context changes first. If a parent context changed
4061 // and the primary children's consumer fibers were discarded
4062 // during initial mount suspension, normal propagation can't find
4063 // them. In that case we conservatively retry the boundary — the
4064 // re-mounted children will read the updated context value.
4065 const contextChanged = lazilyPropagateParentContextChanges(
4066 current,
4067 workInProgress,
4068 renderLanes,
4069 );
4070 const primaryChildFragment: Fiber = workInProgress.child as any;
4071 const primaryChildLanes = primaryChildFragment.childLanes;
4072 if (
4073 contextChanged ||
4074 includesSomeLane(renderLanes, primaryChildLanes)
4075 ) {
4076 // The primary children have pending work. Use the normal path
4077 // to attempt to render the primary children again.
4078 return updateSuspenseComponent(current, workInProgress, renderLanes);
4079 } else {
4080 // The primary child fragment does not have pending work marked
4081 // on it
4082 pushPrimaryTreeSuspenseHandler(workInProgress);
4083 // The primary children do not have pending work with sufficient
4084 // priority. Bailout.
4085 const child = bailoutOnAlreadyFinishedWork(
4086 current,
4087 workInProgress,
4088 renderLanes,
4089 );
4090 if (child !== null) {
4091 // The fallback children have pending work. Skip over the
4092 // primary children and work on the fallback.
4093 return child.sibling;
4094 } else {
4095 // Note: We can return `null` here because we already checked
4096 // whether there were nested context consumers, via the call to
4097 // `bailoutOnAlreadyFinishedWork` above.
4098 return null;
4099 }
4100 }
4101 } else {
4102 pushPrimaryTreeSuspenseHandler(workInProgress);
4103 }
4104 break;
4105 }
4106 case SuspenseListComponent: {
4107 if (workInProgress.flags & DidCapture) {
4108 // Second pass caught.
4109 return updateSuspenseListComponent(
4110 current,
4111 workInProgress,
4112 renderLanes,
4113 );
4114 }
4115 const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
4116
4117 let hasChildWork = includesSomeLane(
4118 renderLanes,
4119 workInProgress.childLanes,
4120 );
4121
4122 if (!hasChildWork) {
4123 // Context changes may not have been propagated yet. We need to do
4124 // that now, before we can decide whether to bail out.
4125 // TODO: We use `childLanes` as a heuristic for whether there is
4126 // remaining work in a few places, including
4127 // `bailoutOnAlreadyFinishedWork` and
4128 // `updateDehydratedSuspenseComponent`. We should maybe extract this
4129 // into a dedicated function.
4130 lazilyPropagateParentContextChanges(
4131 current,
4132 workInProgress,
4133 renderLanes,
4134 );
4135 hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
4136 }
4137
4138 if (didSuspendBefore) {
4139 if (hasChildWork) {
4140 // If something was in fallback state last time, and we have all the
4141 // same children then we're still in progressive loading state.
4142 // Something might get unblocked by state updates or retries in the
4143 // tree which will affect the tail. So we need to use the normal
4144 // path to compute the correct tail.
4145 return updateSuspenseListComponent(
4146 current,
4147 workInProgress,
4148 renderLanes,
4149 );
4150 }
4151 // If none of the children had any work, that means that none of
4152 // them got retried so they'll still be blocked in the same way
4153 // as before. We can fast bail out.
4154 workInProgress.flags |= DidCapture;
4155 }
4156
4157 // If nothing suspended before and we're rendering the same children,
4158 // then the tail doesn't matter. Anything new that suspends will work
4159 // in the "together" mode, so we can continue from the state we had.
4160 const renderState = workInProgress.memoizedState;
4161 if (renderState !== null) {
4162 // Reset to the "together" mode in case we've started a different
4163 // update in the past but didn't complete it.
4164 renderState.rendering = null;
4165 renderState.tail = null;
4166 renderState.lastEffect = null;
4167 }
4168 pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
4169
4170 if (hasChildWork) {
4171 break;
4172 } else {
4173 // If none of the children had any work, that means that none of
4174 // them got retried so they'll still be blocked in the same way
4175 // as before. We can fast bail out.
4176 return null;
4177 }
4178 }
4179 case OffscreenComponent: {
4180 // Need to check if the tree still needs to be deferred. This is
4181 // almost identical to the logic used in the normal update path,
4182 // so we'll just enter that. The only difference is we'll bail out
4183 // at the next level instead of this one, because the child props
4184 // have not changed. Which is fine.
4185 // TODO: Probably should refactor `beginWork` to split the bailout
4186 // path from the normal path. I'm tempted to do a labeled break here
4187 // but I won't :)
4188 workInProgress.lanes = NoLanes;
4189 return updateOffscreenComponent(
4190 current,
4191 workInProgress,
4192 renderLanes,
4193 workInProgress.pendingProps,
4194 );
4195 }
4196 case CacheComponent: {
4197 const cache: Cache = current.memoizedState.cache;
4198 pushCacheProvider(workInProgress, cache);
4199 break;
4200 }
4201 case TracingMarkerComponent: {
4202 if (enableTransitionTracing) {
4203 const instance: TracingMarkerInstance | null = workInProgress.stateNode;
4204 if (instance !== null) {
4205 pushMarkerInstance(workInProgress, instance);
4206 }
4207 break;
4208 }
4209 // Fallthrough
4210 }
4211 case LegacyHiddenComponent: {
4212 if (enableLegacyHidden) {
4213 workInProgress.lanes = NoLanes;
4214 return updateLegacyHiddenComponent(
4215 current,
4216 workInProgress,
4217 renderLanes,
4218 );
4219 }
4220 // Fallthrough
4221 }
4222 }
4223 return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
4224 }
4225
4226 function beginWork(
4227 current: Fiber | null,
4228 workInProgress: Fiber,
4229 renderLanes: Lanes,
4230 ): Fiber | null {
4231 if (__DEV__) {
4232 if (workInProgress._debugNeedsRemount && current !== null) {
4233 // This will restart the begin phase with a new fiber.
4234 const copiedFiber = createFiberFromTypeAndProps(
4235 // Remount from the fiber's outermost identity; mounting resolves
4236 // any inner types to their latest implementations.
4237 resolveTypeForHotReloading(workInProgress.elementType),
4238 workInProgress.key,
4239 workInProgress.pendingProps,
4240 workInProgress._debugOwner || null,
4241 workInProgress.mode,
4242 workInProgress.lanes,
4243 );
4244 copiedFiber._debugStack = workInProgress._debugStack;
4245 copiedFiber._debugTask = workInProgress._debugTask;
4246 return remountFiber(current, workInProgress, copiedFiber);
4247 }
4248 }
4249
4250 if (current !== null) {
4251 const oldProps = current.memoizedProps;
4252 const newProps = workInProgress.pendingProps;
4253
4254 if (
4255 oldProps !== newProps ||
4256 hasLegacyContextChanged() ||
4257 // Force a re-render if the implementation changed due to hot reload:
4258 (__DEV__ ? workInProgress.type !== current.type : false)
4259 ) {
4260 // If props or context changed, mark the fiber as having performed work.
4261 // This may be unset if the props are determined to be equal later (memo).
4262 didReceiveUpdate = true;
4263 } else {
4264 // Neither props nor legacy context changes. Check if there's a pending
4265 // update or context change.
4266 const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
4267 current,
4268 renderLanes,
4269 );
4270 if (
4271 !hasScheduledUpdateOrContext &&
4272 // If this is the second pass of an error or suspense boundary, there
4273 // may not be work scheduled on `current`, so we check for this flag.
4274 (workInProgress.flags & DidCapture) === NoFlags
4275 ) {
4276 // No pending updates or context. Bail out now.
4277 didReceiveUpdate = false;
4278 return attemptEarlyBailoutIfNoScheduledUpdate(
4279 current,
4280 workInProgress,
4281 renderLanes,
4282 );
4283 }
4284 if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
4285 // This is a special case that only exists for legacy mode.
4286 // See https://github.com/facebook/react/pull/19216.
4287 didReceiveUpdate = true;
4288 } else {
4289 // An update was scheduled on this fiber, but there are no new props
4290 // nor legacy context. Set this to false. If an update queue or context
4291 // consumer produces a changed value, it will set this to true. Otherwise,
4292 // the component will assume the children have not changed and bail out.
4293 didReceiveUpdate = false;
4294 }
4295 }
4296 } else {
4297 didReceiveUpdate = false;
4298
4299 if (getIsHydrating() && isForkedChild(workInProgress)) {
4300 // Check if this child belongs to a list of muliple children in
4301 // its parent.
4302 //
4303 // In a true multi-threaded implementation, we would render children on
4304 // parallel threads. This would represent the beginning of a new render
4305 // thread for this subtree.
4306 //
4307 // We only use this for id generation during hydration, which is why the
4308 // logic is located in this special branch.
4309 const slotIndex = workInProgress.index;
4310 const numberOfForks = getForksAtLevel(workInProgress);
4311 pushTreeId(workInProgress, numberOfForks, slotIndex);
4312 }
4313 }
4314
4315 // Before entering the begin phase, clear pending update priority.
4316 // TODO: This assumes that we're about to evaluate the component and process
4317 // the update queue. However, there's an exception: SimpleMemoComponent
4318 // sometimes bails out later in the begin phase. This indicates that we should
4319 // move this assignment out of the common path and into each branch.
4320 workInProgress.lanes = NoLanes;
4321
4322 switch (workInProgress.tag) {
4323 case LazyComponent: {
4324 const elementType = workInProgress.elementType;
4325 return mountLazyComponent(
4326 current,
4327 workInProgress,
4328 elementType,
4329 renderLanes,
4330 );
4331 }
4332 case FunctionComponent: {
4333 const Component = workInProgress.type;
4334 return updateFunctionComponent(
4335 current,
4336 workInProgress,
4337 Component,
4338 workInProgress.pendingProps,
4339 renderLanes,
4340 );
4341 }
4342 case ClassComponent: {
4343 const Component = workInProgress.type;
4344 const unresolvedProps = workInProgress.pendingProps;
4345 const resolvedProps = resolveClassComponentProps(
4346 Component,
4347 unresolvedProps,
4348 );
4349 return updateClassComponent(
4350 current,
4351 workInProgress,
4352 Component,
4353 resolvedProps,
4354 renderLanes,
4355 );
4356 }
4357 case HostRoot:
4358 return updateHostRoot(current, workInProgress, renderLanes);
4359 case HostHoistable:
4360 // $FlowFixMe[constant-condition]
4361 if (supportsResources) {
4362 return updateHostHoistable(current, workInProgress, renderLanes);
4363 }
4364 // Fall through
4365 case HostSingleton:
4366 // $FlowFixMe[constant-condition]
4367 if (supportsSingletons) {
4368 return updateHostSingleton(current, workInProgress, renderLanes);
4369 }
4370 // Fall through
4371 case HostComponent:
4372 return updateHostComponent(current, workInProgress, renderLanes);
4373 case HostText:
4374 return updateHostText(current, workInProgress);
4375 case SuspenseComponent:
4376 return updateSuspenseComponent(current, workInProgress, renderLanes);
4377 case HostPortal:
4378 return updatePortalComponent(current, workInProgress, renderLanes);
4379 case ForwardRef: {
4380 return updateForwardRef(
4381 current,
4382 workInProgress,
4383 workInProgress.type,
4384 workInProgress.pendingProps,
4385 renderLanes,
4386 );
4387 }
4388 case Fragment:
4389 return updateFragment(current, workInProgress, renderLanes);
4390 case Mode:
4391 return updateMode(current, workInProgress, renderLanes);
4392 case Profiler:
4393 return updateProfiler(current, workInProgress, renderLanes);
4394 case ContextProvider:
4395 return updateContextProvider(current, workInProgress, renderLanes);
4396 case ContextConsumer:
4397 return updateContextConsumer(current, workInProgress, renderLanes);
4398 case MemoComponent: {
4399 return updateMemoComponent(
4400 current,
4401 workInProgress,
4402 workInProgress.type,
4403 workInProgress.pendingProps,
4404 renderLanes,
4405 );
4406 }
4407 case SimpleMemoComponent: {
4408 return updateSimpleMemoComponent(
4409 current,
4410 workInProgress,
4411 workInProgress.type,
4412 workInProgress.pendingProps,
4413 renderLanes,
4414 );
4415 }
4416 case IncompleteClassComponent: {
4417 if (disableLegacyMode) {
4418 break;
4419 }
4420 const Component = workInProgress.type;
4421 const unresolvedProps = workInProgress.pendingProps;
4422 const resolvedProps = resolveClassComponentProps(
4423 Component,
4424 unresolvedProps,
4425 );
4426 return mountIncompleteClassComponent(
4427 current,
4428 workInProgress,
4429 Component,
4430 resolvedProps,
4431 renderLanes,
4432 );
4433 }
4434 case IncompleteFunctionComponent: {
4435 if (disableLegacyMode) {
4436 break;
4437 }
4438 const Component = workInProgress.type;
4439 const unresolvedProps = workInProgress.pendingProps;
4440 const resolvedProps = resolveClassComponentProps(
4441 Component,
4442 unresolvedProps,
4443 );
4444 return mountIncompleteFunctionComponent(
4445 current,
4446 workInProgress,
4447 Component,
4448 resolvedProps,
4449 renderLanes,
4450 );
4451 }
4452 case SuspenseListComponent: {
4453 return updateSuspenseListComponent(current, workInProgress, renderLanes);
4454 }
4455 case ScopeComponent: {
4456 if (enableScopeAPI) {
4457 return updateScopeComponent(current, workInProgress, renderLanes);
4458 }
4459 break;
4460 }
4461 case ActivityComponent: {
4462 return updateActivityComponent(current, workInProgress, renderLanes);
4463 }
4464 case OffscreenComponent: {
4465 return updateOffscreenComponent(
4466 current,
4467 workInProgress,
4468 renderLanes,
4469 workInProgress.pendingProps,
4470 );
4471 }
4472 case LegacyHiddenComponent: {
4473 if (enableLegacyHidden) {
4474 return updateLegacyHiddenComponent(
4475 current,
4476 workInProgress,
4477 renderLanes,
4478 );
4479 }
4480 break;
4481 }
4482 case CacheComponent: {
4483 return updateCacheComponent(current, workInProgress, renderLanes);
4484 }
4485 case TracingMarkerComponent: {
4486 if (enableTransitionTracing) {
4487 return updateTracingMarkerComponent(
4488 current,
4489 workInProgress,
4490 renderLanes,
4491 );
4492 }
4493 break;
4494 }
4495 case ViewTransitionComponent: {
4496 if (enableViewTransition) {
4497 return updateViewTransition(current, workInProgress, renderLanes);
4498 }
4499 break;
4500 }
4501 case Throw: {
4502 // This represents a Component that threw in the reconciliation phase.
4503 // So we'll rethrow here. This might be a Thenable.
4504 throw workInProgress.pendingProps;
4505 }
4506 }
4507
4508 throw new Error(
4509 `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
4510 'React. Please file an issue.',
4511 );
4512 }
4513
4514 export {beginWork};