/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
ReactProviderType,
ReactContext,
ReactNodeList,
} from 'shared/ReactTypes';
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {Lanes, Lane} from './ReactFiberLane';
import type {
SuspenseState,
SuspenseListRenderState,
SuspenseListTailMode,
} from './ReactFiberSuspenseComponent';
import type {SuspenseContext} from './ReactFiberSuspenseContext';
import type {
OffscreenProps,
OffscreenState,
OffscreenQueue,
OffscreenInstance,
} from './ReactFiberActivityComponent';
import {OffscreenDetached} from './ReactFiberActivityComponent';
import type {
Cache,
CacheComponentState,
SpawnedCachePool,
} from './ReactFiberCacheComponent';
import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
import type {RootState} from './ReactFiberRoot';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
import type {TransitionStatus} from './ReactFiberConfig';
import type {Hook} from './ReactFiberHooks';
import checkPropTypes from 'shared/checkPropTypes';
import {
markComponentRenderStarted,
markComponentRenderStopped,
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook';
import {
IndeterminateComponent,
FunctionComponent,
ClassComponent,
HostRoot,
HostComponent,
HostHoistable,
HostSingleton,
HostText,
HostPortal,
ForwardRef,
Fragment,
Mode,
ContextProvider,
ContextConsumer,
Profiler,
SuspenseComponent,
SuspenseListComponent,
MemoComponent,
SimpleMemoComponent,
LazyComponent,
IncompleteClassComponent,
ScopeComponent,
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {
NoFlags,
PerformedWork,
Placement,
Hydrating,
ContentReset,
DidCapture,
Update,
Ref,
RefStatic,
ChildDeletion,
ForceUpdateForLegacySuspense,
StaticMask,
ShouldCapture,
ForceClientRender,
Passive,
} from './ReactFiberFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
debugRenderPhaseSideEffectsForStrictMode,
disableLegacyContext,
disableModulePatternComponents,
enableProfilerCommitHooks,
enableProfilerTimer,
enableScopeAPI,
enableCache,
enableLazyContextPropagation,
enableSchedulingProfiler,
enableTransitionTracing,
enableLegacyHidden,
enableCPUSuspense,
enableFloat,
enableHostSingletons,
enableFormActions,
enableAsyncActions,
enablePostpone,
} from 'shared/ReactFeatureFlags';
import isArray from 'shared/isArray';
import shallowEqual from 'shared/shallowEqual';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
import {
getCurrentFiberOwnerNameInDevOrNull,
setIsRendering,
} from './ReactCurrentFiber';
import {
resolveFunctionForHotReloading,
resolveForwardRefForHotReloading,
resolveClassForHotReloading,
} from './ReactFiberHotReloading';
import {
mountChildFibers,
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
import {
processUpdateQueue,
cloneUpdateQueue,
initializeUpdateQueue,
enqueueCapturedUpdate,
} from './ReactFiberClassUpdateQueue';
import {
NoLane,
NoLanes,
SyncLane,
OffscreenLane,
DefaultHydrationLane,
SomeRetryLane,
includesSomeLane,
laneToLanes,
removeLanes,
mergeLanes,
getBumpedLaneForHydration,
pickArbitraryLane,
} from './ReactFiberLane';
import {
ConcurrentMode,
NoMode,
ProfileMode,
StrictLegacyMode,
} from './ReactTypeOfMode';
import {
shouldSetTextContent,
isSuspenseInstancePending,
isSuspenseInstanceFallback,
getSuspenseInstanceFallbackErrorDetails,
registerSuspenseInstanceRetry,
supportsHydration,
supportsResources,
supportsSingletons,
isPrimaryRenderer,
getResource,
createHoistableInstance,
} from './ReactFiberConfig';
import type {SuspenseInstance} from './ReactFiberConfig';
import {shouldError, shouldSuspend} from './ReactFiberReconciler';
import {
pushHostContext,
pushHostContainer,
getRootHostContainer,
HostTransitionContext,
} from './ReactFiberHostContext';
import {
suspenseStackCursor,
pushSuspenseListContext,
ForceSuspenseFallback,
hasSuspenseListContext,
setDefaultShallowSuspenseListContext,
setShallowSuspenseListContext,
pushPrimaryTreeSuspenseHandler,
pushFallbackTreeSuspenseHandler,
pushOffscreenSuspenseHandler,
reuseSuspenseHandlerOnStack,
popSuspenseHandler,
} from './ReactFiberSuspenseContext';
import {
pushHiddenContext,
reuseHiddenContextOnStack,
} from './ReactFiberHiddenContext';
import {findFirstSuspended} from './ReactFiberSuspenseComponent';
import {
pushProvider,
propagateContextChange,
lazilyPropagateParentContextChanges,
propagateParentContextChangesToDeferredTree,
checkIfContextChanged,
readContext,
prepareToReadContext,
scheduleContextWorkOnParentPath,
} from './ReactFiberNewContext';
import {
renderWithHooks,
checkDidRenderIdHook,
bailoutHooks,
replaySuspendedComponentWithHooks,
renderTransitionAwareHostComponentWithHooks,
} from './ReactFiberHooks';
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
import {
getMaskedContext,
getUnmaskedContext,
hasContextChanged as hasLegacyContextChanged,
pushContextProvider as pushLegacyContextProvider,
isContextProvider as isLegacyContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
} from './ReactFiberContext';
import {
getIsHydrating,
enterHydrationState,
reenterHydrationStateFromDehydratedSuspenseInstance,
resetHydrationState,
claimHydratableSingleton,
tryToClaimNextHydratableInstance,
tryToClaimNextHydratableTextInstance,
tryToClaimNextHydratableSuspenseInstance,
warnIfHydrating,
queueHydrationError,
} from './ReactFiberHydrationContext';
import {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
resumeMountClassInstance,
updateClassInstance,
} from './ReactFiberClassComponent';
import {resolveDefaultProps} from './ReactFiberLazyComponent';
import {
resolveLazyComponentTag,
createFiberFromTypeAndProps,
createFiberFromFragment,
createFiberFromOffscreen,
createWorkInProgress,
isSimpleFunctionComponent,
} from './ReactFiber';
import {
retryDehydratedSuspenseBoundary,
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
markSkippedUpdateLanes,
getWorkInProgressRoot,
} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent';
import {
createCapturedValue,
createCapturedValueAtFiber,
type CapturedValue,
} from './ReactCapturedValue';
import {createClassErrorUpdate} from './ReactFiberThrow';
import is from 'shared/objectIs';
import {
getForksAtLevel,
isForkedChild,
pushTreeId,
pushMaterializedTreeId,
} from './ReactFiberTreeContext';
import {
requestCacheFromPool,
pushRootTransition,
getSuspendedCache,
pushTransition,
getOffscreenDeferredCache,
getPendingTransitions,
} from './ReactFiberTransition';
import {
getMarkerInstances,
pushMarkerInstance,
pushRootMarkerInstance,
TransitionTracingMarker,
} from './ReactFiberTracingMarkerComponent';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
// A special exception that's used to unwind the stack when an update flows
// into a dehydrated boundary.
export const SelectiveHydrationException: mixed = new Error(
"This is not a real error. It's an implementation detail of React's " +
"selective hydration feature. If this leaks into userspace, it's a bug in " +
'React. Please file an issue.',
);
let didReceiveUpdate: boolean = false;
let didWarnAboutBadClass;
let didWarnAboutModulePatternComponent;
let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
export let didWarnAboutReassigningProps: boolean;
let didWarnAboutRevealOrder;
let didWarnAboutTailOptions;
let didWarnAboutDefaultPropsOnFunctionComponent;
if (__DEV__) {
didWarnAboutBadClass = ({}: {[string]: boolean});
didWarnAboutModulePatternComponent = ({}: {[string]: boolean});
didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean});
didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean});
didWarnAboutFunctionRefs = ({}: {[string]: boolean});
didWarnAboutReassigningProps = false;
didWarnAboutRevealOrder = ({}: {[empty]: boolean});
didWarnAboutTailOptions = ({}: {[string]: boolean});
didWarnAboutDefaultPropsOnFunctionComponent = ({}: {[string]: boolean});
}
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
// effect of all current children being unmounted; even if the type and key
// are the same, the old child is unmounted and a new child is created.
//
// To do this, we're going to go through the reconcile algorithm twice. In
// the first pass, we schedule a deletion for all the current children by
// passing null.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
null,
renderLanes,
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
// the effect of remounting all children regardless of whether their
// identities match.
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
}
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(Component),
);
}
}
}
const render = Component.render;
const ref = workInProgress.ref;
// The rest is a fork of updateFunctionComponent
let nextChildren;
let hasId;
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderLanes,
);
hasId = checkDidRenderIdHook();
setIsRendering(false);
} else {
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderLanes,
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
if (current === null) {
const type = Component.type;
if (
isSimpleFunctionComponent(type) &&
Component.compare === null &&
// SimpleMemoComponent codepath doesn't resolve outer props either.
Component.defaultProps === undefined
) {
let resolvedType = type;
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(type);
}
// If this is a plain function component without default props,
// and with only the default shallow comparison, we upgrade it
// to a SimpleMemoComponent to allow fast path updates.
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = resolvedType;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, type);
}
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
if (__DEV__) {
const innerPropTypes = type.propTypes;
if (innerPropTypes) {
// Inner memo component props aren't currently validated in createElement.
// We could move it there, but we'd still need this for lazy code path.
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(type),
);
}
if (Component.defaultProps !== undefined) {
const componentName = getComponentNameFromType(type) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from memo components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
}
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
null,
workInProgress,
workInProgress.mode,
renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
return child;
}
if (__DEV__) {
const type = Component.type;
const innerPropTypes = type.propTypes;
if (innerPropTypes) {
// Inner memo component props aren't currently validated in createElement.
// We could move it there, but we'd still need this for lazy code path.
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(type),
);
}
}
const currentChild = ((current.child: any): Fiber); // This is always exactly one child
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
const newChild = createWorkInProgress(currentChild, nextProps);
newChild.ref = workInProgress.ref;
newChild.return = workInProgress;
workInProgress.child = newChild;
return newChild;
}
function updateSimpleMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens when the inner render suspends.
// We'll need to figure out if this is fine or can cause issues.
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
let outerMemoType = workInProgress.elementType;
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) {
// We warn when you define propTypes on lazy()
// so let's just skip over it to find memo() outer wrapper.
// Inner props for memo are validated later.
const lazyComponent: LazyComponentType<any, any> = outerMemoType;
const payload = lazyComponent._payload;
const init = lazyComponent._init;
try {
outerMemoType = init(payload);
} catch (x) {
outerMemoType = null;
}
// Inner propTypes will be validated in the function component path.
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
'prop',
getComponentNameFromType(outerMemoType),
);
}
}
}
}
if (current !== null) {
const prevProps = current.memoizedProps;
if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref &&
// Prevent bailout if the implementation changed due to hot reload.
(__DEV__ ? workInProgress.type === current.type : true)
) {
didReceiveUpdate = false;
// The props are shallowly equal. Reuse the previous props object, like we
// would during a normal fiber bailout.
//
// We don't have strong guarantees that the props object is referentially
// equal during updates where we can't bail out anyway — like if the props
// are shallowly equal, but there's a local state or context update in the
// same batch.
//
// However, as a principle, we should aim to make the behavior consistent
// across different ways of memoizing a component. For example, React.memo
// has a different internal Fiber layout if you pass a normal function
// component (SimpleMemoComponent) versus if you pass a different type
// like forwardRef (MemoComponent). But this is an implementation detail.
// Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
// affect whether the props object is reused during a bailout.
workInProgress.pendingProps = nextProps = prevProps;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
// The pending lanes were cleared at the beginning of beginWork. We're
// about to bail out, but there might be other lanes that weren't
// included in the current render. Usually, the priority level of the
// remaining updates is accumulated during the evaluation of the
// component (i.e. when processing the update queue). But since since
// we're bailing out early *without* evaluating the component, we need
// to account for it here, too. Reset to the value of the current fiber.
// NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
// because a MemoComponent fiber does not have hooks or an update queue;
// rather, it wraps around an inner component, which may or may not
// contains hooks.
// TODO: Move the reset at in beginWork out of the common path so that
// this is no longer necessary.
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
}
}
}
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
function updateOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: OffscreenProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
const nextIsDetached =
(workInProgress.stateNode._pendingVisibility & OffscreenDetached) !== 0;
const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;
markRef(current, workInProgress);
if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden &&
nextProps.mode === 'unstable-defer-without-hiding') ||
nextIsDetached
) {
// Rendering a hidden tree.
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (didSuspend) {
// Something suspended inside a hidden tree
// Include the base lanes from the last render
const nextBaseLanes =
prevState !== null
? mergeLanes(prevState.baseLanes, renderLanes)
: renderLanes;
if (current !== null) {
// Reset to the current children
let currentChild = (workInProgress.child = current.child);
// The current render suspended, but there may be other lanes with
// pending work. We can't read `childLanes` from the current Offscreen
// fiber because we reset it when it was deferred; however, we can read
// the pending lanes from the child fibers.
let currentChildLanes = NoLanes;
while (currentChild !== null) {
currentChildLanes = mergeLanes(
mergeLanes(currentChildLanes, currentChild.lanes),
currentChild.childLanes,
);
currentChild = currentChild.sibling;
}
const lanesWeJustAttempted = nextBaseLanes;
const remainingChildLanes = removeLanes(
currentChildLanes,
lanesWeJustAttempted,
);
workInProgress.childLanes = remainingChildLanes;
} else {
workInProgress.childLanes = NoLanes;
workInProgress.child = null;
}
return deferHiddenOffscreenComponent(
current,
workInProgress,
nextBaseLanes,
renderLanes,
);
}
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
// In legacy sync mode, don't defer the subtree. Render it now.
// TODO: Consider how Offscreen should work with transitions in the future
const nextState: OffscreenState = {
baseLanes: NoLanes,
cachePool: null,
};
workInProgress.memoizedState = nextState;
if (enableCache) {
// push the cache pool even though we're going to bail out
// because otherwise there'd be a context mismatch
if (current !== null) {
pushTransition(workInProgress, null, null);
}
}
reuseHiddenContextOnStack(workInProgress);
pushOffscreenSuspenseHandler(workInProgress);
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
// We're hidden, and we're not rendering at Offscreen. We will bail out
// and resume this tree later.
// Schedule this fiber to re-render at Offscreen priority
workInProgress.lanes = workInProgress.childLanes =
laneToLanes(OffscreenLane);
// Include the base lanes from the last render
const nextBaseLanes =
prevState !== null
? mergeLanes(prevState.baseLanes, renderLanes)
: renderLanes;
return deferHiddenOffscreenComponent(
current,
workInProgress,
nextBaseLanes,
renderLanes,
);
} else {
// This is the second render. The surrounding visible content has already
// committed. Now we resume rendering the hidden tree.
// Rendering at offscreen, so we can clear the base lanes.
const nextState: OffscreenState = {
baseLanes: NoLanes,
cachePool: null,
};
workInProgress.memoizedState = nextState;
if (enableCache && current !== null) {
// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
const prevCachePool = prevState !== null ? prevState.cachePool : null;
// TODO: Consider if and how Offscreen pre-rendering should
// be attributed to the transition that spawned it
pushTransition(workInProgress, prevCachePool, null);
}
// Push the lanes that were skipped when we bailed out.
if (prevState !== null) {
pushHiddenContext(workInProgress, prevState);
} else {
reuseHiddenContextOnStack(workInProgress);
}
pushOffscreenSuspenseHandler(workInProgress);
}
} else {
// Rendering a visible tree.
if (prevState !== null) {
// We're going from hidden -> visible.
let prevCachePool = null;
if (enableCache) {
// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
prevCachePool = prevState.cachePool;
}
let transitions = null;
if (enableTransitionTracing) {
// We have now gone from hidden to visible, so any transitions should
// be added to the stack to get added to any Offscreen/suspense children
const instance: OffscreenInstance | null = workInProgress.stateNode;
if (instance !== null && instance._transitions != null) {
transitions = Array.from(instance._transitions);
}
}
pushTransition(workInProgress, prevCachePool, transitions);
// Push the lanes that were skipped when we bailed out.
pushHiddenContext(workInProgress, prevState);
reuseSuspenseHandlerOnStack(workInProgress);
// Since we're not hidden anymore, reset the state
workInProgress.memoizedState = null;
} else {
// We weren't previously hidden, and we still aren't, so there's nothing
// special to do. Need to push to the stack regardless, though, to avoid
// a push/pop misalignment.
if (enableCache) {
// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
if (current !== null) {
pushTransition(workInProgress, null, null);
}
}
// We're about to bail out, but we need to push this to the stack anyway
// to avoid a push/pop misalignment.
reuseHiddenContextOnStack(workInProgress);
reuseSuspenseHandlerOnStack(workInProgress);
}
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function deferHiddenOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
nextBaseLanes: Lanes,
renderLanes: Lanes,
) {
const nextState: OffscreenState = {
baseLanes: nextBaseLanes,
// Save the cache pool so we can resume later.
cachePool: enableCache ? getOffscreenDeferredCache() : null,
};
workInProgress.memoizedState = nextState;
if (enableCache) {
// push the cache pool even though we're going to bail out
// because otherwise there'd be a context mismatch
if (current !== null) {
pushTransition(workInProgress, null, null);
}
}
// We're about to bail out, but we need to push this to the stack anyway
// to avoid a push/pop misalignment.
reuseHiddenContextOnStack(workInProgress);
pushOffscreenSuspenseHandler(workInProgress);
if (enableLazyContextPropagation && current !== null) {
// Since this tree will resume rendering in a separate render, we need
// to propagate parent contexts now so we don't lose track of which
// ones changed.
propagateParentContextChangesToDeferredTree(
current,
workInProgress,
renderLanes,
);
}
return null;
}
// Note: These happen to have identical begin phases, for now. We shouldn't hold
// ourselves to this constraint, though. If the behavior diverges, we should
// fork the function.
const updateLegacyHiddenComponent = updateOffscreenComponent;
function updateCacheComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (!enableCache) {
return null;
}
prepareToReadContext(workInProgress, renderLanes);
const parentCache = readContext(CacheContext);
if (current === null) {
// Initial mount. Request a fresh cache from the pool.
const freshCache = requestCacheFromPool(renderLanes);
const initialState: CacheComponentState = {
parent: parentCache,
cache: freshCache,
};
workInProgress.memoizedState = initialState;
initializeUpdateQueue(workInProgress);
pushCacheProvider(workInProgress, freshCache);
} else {
// Check for updates
if (includesSomeLane(current.lanes, renderLanes)) {
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, null, null, renderLanes);
}
const prevState: CacheComponentState = current.memoizedState;
const nextState: CacheComponentState = workInProgress.memoizedState;
// Compare the new parent cache to the previous to see detect there was
// a refresh.
if (prevState.parent !== parentCache) {
// Refresh in parent. Update the parent.
const derivedState: CacheComponentState = {
parent: parentCache,
cache: parentCache,
};
// Copied from getDerivedStateFromProps implementation. Once the update
// queue is empty, persist the derived state onto the base state.
workInProgress.memoizedState = derivedState;
if (workInProgress.lanes === NoLanes) {
const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
workInProgress.memoizedState = updateQueue.baseState = derivedState;
}
pushCacheProvider(workInProgress, parentCache);
// No need to propagate a context change because the refreshed parent
// already did.
} else {
// The parent didn't refresh. Now check if this cache did.
const nextCache = nextState.cache;
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
// This cache refreshed. Propagate a context change.
propagateContextChange(workInProgress, CacheContext, renderLanes);
}
}
}
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
// This should only be called if the name changes
function updateTracingMarkerComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (!enableTransitionTracing) {
return null;
}
// TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
// A tracing marker is only associated with the transitions that rendered
// or updated it, so we can create a new set of transitions each time
if (current === null) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const markerInstance: TracingMarkerInstance = {
tag: TransitionTracingMarker,
transitions: new Set(currentTransitions),
pendingBoundaries: null,
name: workInProgress.pendingProps.name,
aborts: null,
};
workInProgress.stateNode = markerInstance;
// We call the marker complete callback when all child suspense boundaries resolve.
// We do this in the commit phase on Offscreen. If the marker has no child suspense
// boundaries, we need to schedule a passive effect to make sure we call the marker
// complete callback.
workInProgress.flags |= Passive;
}
} else {
if (__DEV__) {
if (current.memoizedProps.name !== workInProgress.pendingProps.name) {
console.error(
'Changing the name of a tracing marker after mount is not supported. ' +
'To remount the tracing marker, pass it a new key.',
);
}
}
}
const instance: TracingMarkerInstance | null = workInProgress.stateNode;
if (instance !== null) {
pushMarkerInstance(workInProgress, instance);
}
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (enableProfilerTimer) {
workInProgress.flags |= Update;
if (enableProfilerCommitHooks) {
// Reset effect durations for the next eventual effect phase.
// These are reset during render to allow the DevTools commit hook a chance to read them,
const stateNode = workInProgress.stateNode;
stateNode.effectDuration = 0;
stateNode.passiveEffectDuration = 0;
}
}
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
// Schedule a Ref effect
workInProgress.flags |= Ref;
workInProgress.flags |= RefStatic;
}
}
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(Component),
);
}
}
}
let context;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}
let nextChildren;
let hasId;
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
setIsRendering(false);
} else {
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
export function replayFunctionComponent(
current: Fiber | null,
workInProgress: Fiber,
nextProps: any,
Component: any,
secondArg: any,
renderLanes: Lanes,
): Fiber | null {
// This function is used to replay a component that previously suspended,
// after its data resolves. It's a simplified version of
// updateFunctionComponent that reuses the hooks from the previous attempt.
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
const nextChildren = replaySuspendedComponentWithHooks(
current,
workInProgress,
Component,
nextProps,
secondArg,
);
const hasId = checkDidRenderIdHook();
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
if (__DEV__) {
// This is used by DevTools to force a boundary to error.
switch (shouldError(workInProgress)) {
case false: {
const instance = workInProgress.stateNode;
const ctor = workInProgress.type;
// TODO This way of resetting the error boundary state is a hack.
// Is there a better way to do this?
const tempInstance = new ctor(
workInProgress.memoizedProps,
instance.context,
);
const state = tempInstance.state;
instance.updater.enqueueSetState(instance, state, null);
break;
}
case true: {
workInProgress.flags |= DidCapture;
workInProgress.flags |= ShouldCapture;
// eslint-disable-next-line react-internal/prod-error-codes
const error = new Error('Simulated error coming from DevTools');
const lane = pickArbitraryLane(renderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
createCapturedValueAtFiber(error, workInProgress),
lane,
);
enqueueCapturedUpdate(workInProgress, update);
break;
}
}
if (workInProgress.type !== workInProgress.elementType) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = Component.propTypes;
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(Component),
);
}
}
}
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes,
);
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
if (__DEV__) {
const inst = workInProgress.stateNode;
if (shouldUpdate && inst.props !== nextProps) {
if (!didWarnAboutReassigningProps) {
console.error(
'It looks like %s is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
getComponentNameFromFiber(workInProgress) || 'a component',
);
}
didWarnAboutReassigningProps = true;
}
}
return nextUnitOfWork;
}
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// If we captured an error, but getDerivedStateFromError is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
setIsRendering(true);
nextChildren = instance.render();
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictLegacyMode
) {
setIsStrictModeForDevtools(true);
try {
instance.render();
} finally {
setIsStrictModeForDevtools(false);
}
}
setIsRendering(false);
} else {
nextChildren = instance.render();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
workInProgress.memoizedState = instance.state;
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
function pushHostRootContext(workInProgress: Fiber) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
// Should always be set
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}
function updateHostRoot(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostRootContext(workInProgress);
if (current === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState: RootState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);
if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}
if (enableCache) {
const nextCache: Cache = nextState.cache;
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
// The root cache refreshed.
propagateContextChange(workInProgress, CacheContext, renderLanes);
}
}
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (supportsHydration && prevState.isDehydrated) {
// This is a hydration root whose shell has not yet hydrated. We should
// attempt to hydrate.
// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
};
const updateQueue: UpdateQueue<RootState> =
(workInProgress.updateQueue: any);
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;
if (workInProgress.flags & ForceClientRender) {
// Something errored during a previous attempt to hydrate the shell, so we
// forced a client render.
const recoverableError = createCapturedValueAtFiber<mixed>(
new Error(
'There was an error while hydrating. Because the error happened outside ' +
'of a Suspense boundary, the entire root will switch to ' +
'client rendering.',
),
workInProgress,
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
nextChildren,
renderLanes,
recoverableError,
);
} else if (nextChildren !== prevChildren) {
const recoverableError = createCapturedValueAtFiber<mixed>(
new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
),
workInProgress,
);
return mountHostRootWithoutHydrating(
current,
workInProgress,
nextChildren,
renderLanes,
recoverableError,
);
} else {
// The outermost shell has not hydrated yet. Start hydrating.
enterHydrationState(workInProgress);
const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
}
} else {
// Root is not dehydrated. Either this is a client-only root, or it
// already hydrated.
resetHydrationState();
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
function mountHostRootWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
nextChildren: ReactNodeList,
renderLanes: Lanes,
recoverableError: CapturedValue<mixed>,
) {
// Revert to client rendering.
resetHydrationState();
queueHydrationError(recoverableError);
workInProgress.flags |= ForceClientRender;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.flags |= ContentReset;
}
if (enableFormActions && enableAsyncActions) {
const memoizedState = workInProgress.memoizedState;
if (memoizedState !== null) {
// This fiber has been upgraded to a stateful component. The only way
// happens currently is for form actions. We use hooks to track the
// pending and error state of the form.
//
// Once a fiber is upgraded to be stateful, it remains stateful for the
// rest of its lifetime.
const newState = renderTransitionAwareHostComponentWithHooks(
current,
workInProgress,
renderLanes,
);
// If the transition state changed, propagate the change to all the
// descendents. We use Context as an implementation detail for this.
//
// This is intentionally set here instead of pushHostContext because
// pushHostContext gets called before we process the state hook, to avoid
// a state mismatch in the event that something suspends.
//
// NOTE: This assumes that there cannot be nested transition providers,
// because the only renderer that implements this feature is React DOM,
// and forms cannot be nested. If we did support nested providers, then
// we would need to push a context value even for host fibers that
// haven't been upgraded yet.
if (isPrimaryRenderer) {
HostTransitionContext._currentValue = newState;
} else {
HostTransitionContext._currentValue2 = newState;
}
if (enableLazyContextPropagation) {
// In the lazy propagation implementation, we don't scan for matching
// consumers until something bails out.
} else {
if (didReceiveUpdate) {
if (current !== null) {
const oldStateHook: Hook = current.memoizedState;
const oldState: TransitionStatus = oldStateHook.memoizedState;
// This uses regular equality instead of Object.is because we assume
// that host transition state doesn't include NaN as a valid type.
if (oldState !== newState) {
propagateContextChange(
workInProgress,
HostTransitionContext,
renderLanes,
);
}
}
}
}
}
}
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateHostHoistable(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
markRef(current, workInProgress);
const currentProps = current === null ? null : current.memoizedProps;
const resource = (workInProgress.memoizedState = getResource(
workInProgress.type,
currentProps,
workInProgress.pendingProps,
));
if (current === null) {
if (!getIsHydrating() && resource === null) {
// This is not a Resource Hoistable and we aren't hydrating so we construct the instance.
workInProgress.stateNode = createHoistableInstance(
workInProgress.type,
workInProgress.pendingProps,
getRootHostContainer(),
workInProgress,
);
}
}
// Resources never have reconciler managed children. It is possible for
// the host implementation of getResource to consider children in the
// resource construction but they will otherwise be discarded. In practice
// this precludes all but the simplest children and Host specific warnings
// should be implemented to warn when children are passsed when otherwise not
// expected
return null;
}
function updateHostSingleton(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);
if (current === null) {
claimHydratableSingleton(workInProgress);
}
const nextChildren = workInProgress.pendingProps.children;
if (current === null && !getIsHydrating()) {
// Similar to Portals we append Singleton children in the commit phase. So we
// Track insertions even on mount.
// TODO: Consider unifying this with how the root works.
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
markRef(current, workInProgress);
return workInProgress.child;
}
function updateHostText(current: null | Fiber, workInProgress: Fiber) {
if (current === null) {
tryToClaimNextHydratableTextInstance(workInProgress);
}
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
return null;
}
function mountLazyComponent(
_current: null | Fiber,
workInProgress: Fiber,
elementType: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
const props = workInProgress.pendingProps;
const lazyComponent: LazyComponentType<any, any> = elementType;
const payload = lazyComponent._payload;
const init = lazyComponent._init;
let Component = init(payload);
// Store the unwrapped component in the type.
workInProgress.type = Component;
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
const resolvedProps = resolveDefaultProps(Component, props);
let child;
switch (resolvedTag) {
case FunctionComponent: {
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
workInProgress.type = Component =
resolveFunctionForHotReloading(Component);
}
child = updateFunctionComponent(
null,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
return child;
}
case ClassComponent: {
if (__DEV__) {
workInProgress.type = Component =
resolveClassForHotReloading(Component);
}
child = updateClassComponent(
null,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
return child;
}
case ForwardRef: {
if (__DEV__) {
workInProgress.type = Component =
resolveForwardRefForHotReloading(Component);
}
child = updateForwardRef(
null,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
return child;
}
case MemoComponent: {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = Component.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentNameFromType(Component),
);
}
}
}
child = updateMemoComponent(
null,
workInProgress,
Component,
resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
renderLanes,
);
return child;
}
}
let hint = '';
if (__DEV__) {
if (
Component !== null &&
typeof Component === 'object' &&
Component.$$typeof === REACT_LAZY_TYPE
) {
hint = ' Did you wrap a component in React.lazy() more than once?';
}
}
// This message intentionally doesn't mention ForwardRef or MemoComponent
// because the fact that it's a separate type of work is an
// implementation detail.
throw new Error(
`Element type is invalid. Received a promise that resolves to: ${Component}. ` +
`Lazy element type must resolve to a class or function.${hint}`,
);
}
function mountIncompleteClassComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
// Promote the fiber to a class and try rendering again.
workInProgress.tag = ClassComponent;
// The rest of this function is a fork of `updateClassComponent`
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderLanes,
);
}
function mountIndeterminateComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: $FlowFixMe,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
const props = workInProgress.pendingProps;
let context;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(
workInProgress,
Component,
false,
);
context = getMaskedContext(workInProgress, unmaskedContext);
}
prepareToReadContext(workInProgress, renderLanes);
let value;
let hasId;
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === 'function'
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutBadClass[componentName]) {
console.error(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}
if (workInProgress.mode & StrictLegacyMode) {
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
}
setIsRendering(true);
ReactCurrentOwner.current = workInProgress;
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
setIsRendering(false);
} else {
value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
if (__DEV__) {
// Support for module components is deprecated and is removed behind a flag.
// Whether or not it would crash later, we want to show a good message in DEV first.
if (
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
}
if (
// Run these checks in production only if the flag is off.
// Eventually we'll delete this branch altogether.
!disableModulePatternComponents &&
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
if (__DEV__) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
console.error(
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// Throw out any hooks that were used.
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext = false;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;
initializeUpdateQueue(workInProgress);
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderLanes);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderLanes,
);
} else {
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
if (__DEV__) {
if (disableLegacyContext && Component.contextTypes) {
console.error(
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use React.createContext() with React.useContext() instead.',
getComponentNameFromType(Component) || 'Unknown',
);
}
}
if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}
reconcileChildren(null, workInProgress, value, renderLanes);
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
}
return workInProgress.child;
}
}
function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
if (__DEV__) {
if (Component) {
if (Component.childContextTypes) {
console.error(
'%s(...): childContextTypes cannot be defined on a function component.',
Component.displayName || Component.name || 'Component',
);
}
}
if (workInProgress.ref !== null) {
let info = '';
const ownerName = getCurrentFiberOwnerNameInDevOrNull();
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
let warningKey = ownerName || '';
const debugSource = workInProgress._debugSource;
if (debugSource) {
warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
}
if (!didWarnAboutFunctionRefs[warningKey]) {
didWarnAboutFunctionRefs[warningKey] = true;
console.error(
'Function components cannot be given refs. ' +
'Attempts to access this ref will fail. ' +
'Did you mean to use React.forwardRef()?%s',
info,
);
}
}
if (Component.defaultProps !== undefined) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from function components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
}
if (
typeof Component.contextType === 'object' &&
Component.contextType !== null
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support contextType.',
componentName,
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
}
}
}
const SUSPENDED_MARKER: SuspenseState = {
dehydrated: null,
treeContext: null,
retryLane: NoLane,
};
function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
return {
baseLanes: renderLanes,
cachePool: getSuspendedCache(),
};
}
function updateSuspenseOffscreenState(
prevOffscreenState: OffscreenState,
renderLanes: Lanes,
): OffscreenState {
let cachePool: SpawnedCachePool | null = null;
if (enableCache) {
const prevCachePool: SpawnedCachePool | null = prevOffscreenState.cachePool;
if (prevCachePool !== null) {
const parentCache = isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2;
if (prevCachePool.parent !== parentCache) {
// Detected a refresh in the parent. This overrides any previously
// suspended cache.
cachePool = {
parent: parentCache,
pool: parentCache,
};
} else {
// We can reuse the cache from last time. The only thing that would have
// overridden it is a parent refresh, which we checked for above.
cachePool = prevCachePool;
}
} else {
// If there's no previous cache pool, grab the current one.
cachePool = getSuspendedCache();
}
}
return {
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
cachePool,
};
}
// TODO: Probably should inline this back
function shouldRemainOnFallback(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// If we're already showing a fallback, there are cases where we need to
// remain on that fallback regardless of whether the content has resolved.
// For example, SuspenseList coordinates when nested content appears.
// TODO: For compatibility with offscreen prerendering, this should also check
// whether the current fiber (if it exists) was visible in the previous tree.
if (current !== null) {
const suspenseState: SuspenseState = current.memoizedState;
if (suspenseState === null) {
// Currently showing content. Don't hide it, even if ForceSuspenseFallback
// is true. More precise name might be "ForceRemainSuspenseFallback".
// Note: This is a factoring smell. Can't remain on a fallback if there's
// no fallback to remain on.
return false;
}
}
// Not currently showing content. Consult the Suspense context.
const suspenseContext: SuspenseContext = suspenseStackCursor.current;
return hasSuspenseListContext(
suspenseContext,
(ForceSuspenseFallback: SuspenseContext),
);
}
function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes: Lanes) {
// TODO: Should not remove render lanes that were pinged during this render
return removeLanes(current.childLanes, renderLanes);
}
function updateSuspenseComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = workInProgress.pendingProps;
// This is used by DevTools to force a boundary to suspend.
if (__DEV__) {
if (shouldSuspend(workInProgress)) {
workInProgress.flags |= DidCapture;
}
}
let showFallback = false;
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (
didSuspend ||
shouldRemainOnFallback(current, workInProgress, renderLanes)
) {
// Something in this boundary's subtree already suspended. Switch to
// rendering the fallback children.
showFallback = true;
workInProgress.flags &= ~DidCapture;
}
// OK, the next part is confusing. We're about to reconcile the Suspense
// boundary's children. This involves some custom reconciliation logic. Two
// main reasons this is so complicated.
//
// First, Legacy Mode has different semantics for backwards compatibility. The
// primary tree will commit in an inconsistent state, so when we do the
// second pass to render the fallback, we do some exceedingly, uh, clever
// hacks to make that not totally break. Like transferring effects and
// deletions from hidden tree. In Concurrent Mode, it's much simpler,
// because we bailout on the primary tree completely and leave it in its old
// state, no effects. Same as what we do for Offscreen (except that
// Offscreen doesn't have the first render pass).
//
// Second is hydration. During hydration, the Suspense fiber has a slightly
// different layout, where the child points to a dehydrated fragment, which
// contains the DOM rendered by the server.
//
// Third, even if you set all that aside, Suspense is like error boundaries in
// that we first we try to render one tree, and if that fails, we render again
// and switch to a different tree. Like a try/catch block. So we have to track
// which branch we're currently rendering. Ideally we would model this using
// a stack.
if (current === null) {
// Initial mount
// Special path for hydration
// If we're currently hydrating, try to hydrate this boundary.
if (getIsHydrating()) {
// We must push the suspense handler context *before* attempting to
// hydrate, to avoid a mismatch in case it errors.
if (showFallback) {
pushPrimaryTreeSuspenseHandler(workInProgress);
} else {
pushFallbackTreeSuspenseHandler(workInProgress);
}
tryToClaimNextHydratableSuspenseInstance(workInProgress);
// This could've been a dehydrated suspense component.
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
if (suspenseState !== null) {
const dehydrated = suspenseState.dehydrated;
if (dehydrated !== null) {
return mountDehydratedSuspenseComponent(
workInProgress,
dehydrated,
renderLanes,
);
}
}
// If hydration didn't succeed, fall through to the normal Suspense path.
// To avoid a stack mismatch we need to pop the Suspense handler that we
// pushed above. This will become less awkward when move the hydration
// logic to its own fiber.
popSuspenseHandler(workInProgress);
}
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);
const fallbackFragment = mountSuspenseFallbackChildren(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
workInProgress.memoizedState = SUSPENDED_MARKER;
if (enableTransitionTracing) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const parentMarkerInstances = getMarkerInstances();
const offscreenQueue: OffscreenQueue | null =
(primaryChildFragment.updateQueue: any);
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else {
offscreenQueue.transitions = currentTransitions;
offscreenQueue.markerInstances = parentMarkerInstances;
}
}
}
return fallbackFragment;
} else if (
enableCPUSuspense &&
typeof nextProps.unstable_expectedLoadTime === 'number'
) {
// This is a CPU-bound tree. Skip this tree and show a placeholder to
// unblock the surrounding content. Then immediately retry after the
// initial commit.
pushFallbackTreeSuspenseHandler(workInProgress);
const fallbackFragment = mountSuspenseFallbackChildren(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
workInProgress.memoizedState = SUSPENDED_MARKER;
// TODO: Transition Tracing is not yet implemented for CPU Suspense.
// Since nothing actually suspended, there will nothing to ping this to
// get it started back up to attempt the next item. While in terms of
// priority this work has the same priority as this current render, it's
// not part of the same transition once the transition has committed. If
// it's sync, we still want to yield so that it can be painted.
// Conceptually, this is really the same as pinging. We can use any
// RetryLane even if it's the one currently rendering since we're leaving
// it behind on this node.
workInProgress.lanes = SomeRetryLane;
return fallbackFragment;
} else {
pushPrimaryTreeSuspenseHandler(workInProgress);
return mountSuspensePrimaryChildren(
workInProgress,
nextPrimaryChildren,
renderLanes,
);
}
} else {
// This is an update.
// Special path for hydration
const prevState: null | SuspenseState = current.memoizedState;
if (prevState !== null) {
const dehydrated = prevState.dehydrated;
if (dehydrated !== null) {
return updateDehydratedSuspenseComponent(
current,
workInProgress,
didSuspend,
nextProps,
dehydrated,
prevState,
renderLanes,
);
}
}
if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);
const nextFallbackChildren = nextProps.fallback;
const nextPrimaryChildren = nextProps.children;
const fallbackChildFragment = updateSuspenseFallbackChildren(
current,
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
const prevOffscreenState: OffscreenState | null = (current.child: any)
.memoizedState;
primaryChildFragment.memoizedState =
prevOffscreenState === null
? mountSuspenseOffscreenState(renderLanes)
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
if (enableTransitionTracing) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const parentMarkerInstances = getMarkerInstances();
const offscreenQueue: OffscreenQueue | null =
(primaryChildFragment.updateQueue: any);
const currentOffscreenQueue: OffscreenQueue | null =
(current.updateQueue: any);
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else if (offscreenQueue === currentOffscreenQueue) {
// If the work-in-progress queue is the same object as current, we
// can't modify it without cloning it first.
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue:
currentOffscreenQueue !== null
? currentOffscreenQueue.retryQueue
: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else {
offscreenQueue.transitions = currentTransitions;
offscreenQueue.markerInstances = parentMarkerInstances;
}
}
}
primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
} else {
pushPrimaryTreeSuspenseHandler(workInProgress);
const nextPrimaryChildren = nextProps.children;
const primaryChildFragment = updateSuspensePrimaryChildren(
current,
workInProgress,
nextPrimaryChildren,
renderLanes,
);
workInProgress.memoizedState = null;
return primaryChildFragment;
}
}
}
function mountSuspensePrimaryChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const primaryChildProps: OffscreenProps = {
mode: 'visible',
children: primaryChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
renderLanes,
);
primaryChildFragment.return = workInProgress;
workInProgress.child = primaryChildFragment;
return primaryChildFragment;
}
function mountSuspenseFallbackChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const progressedPrimaryFragment: Fiber | null = workInProgress.child;
const primaryChildProps: OffscreenProps = {
mode: 'hidden',
children: primaryChildren,
};
let primaryChildFragment;
let fallbackChildFragment;
if (
(mode & ConcurrentMode) === NoMode &&
progressedPrimaryFragment !== null
) {
// In legacy mode, we commit the primary tree as if it successfully
// completed, even though it's in an inconsistent state.
primaryChildFragment = progressedPrimaryFragment;
primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// Reset the durations from the first pass so they aren't included in the
// final amounts. This seems counterintuitive, since we're intentionally
// not measuring part of the render phase, but this makes it match what we
// do in Concurrent Mode.
primaryChildFragment.actualDuration = 0;
primaryChildFragment.actualStartTime = -1;
primaryChildFragment.selfBaseDuration = 0;
primaryChildFragment.treeBaseDuration = 0;
}
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
} else {
primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
NoLanes,
);
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
}
primaryChildFragment.return = workInProgress;
fallbackChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;
return fallbackChildFragment;
}
function mountWorkInProgressOffscreenFiber(
offscreenProps: OffscreenProps,
mode: TypeOfMode,
renderLanes: Lanes,
) {
// The props argument to `createFiberFromOffscreen` is `any` typed, so we use
// this wrapper function to constrain it.
return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null);
}
function updateWorkInProgressOffscreenFiber(
current: Fiber,
offscreenProps: OffscreenProps,
) {
// The props argument to `createWorkInProgress` is `any` typed, so we use this
// wrapper function to constrain it.
return createWorkInProgress(current, offscreenProps);
}
function updateSuspensePrimaryChildren(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const currentPrimaryChildFragment: Fiber = (current.child: any);
const currentFallbackChildFragment: Fiber | null =
currentPrimaryChildFragment.sibling;
const primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentPrimaryChildFragment,
{
mode: 'visible',
children: primaryChildren,
},
);
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
primaryChildFragment.lanes = renderLanes;
}
primaryChildFragment.return = workInProgress;
primaryChildFragment.sibling = null;
if (currentFallbackChildFragment !== null) {
// Delete the fallback child fragment
const deletions = workInProgress.deletions;
if (deletions === null) {
workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.flags |= ChildDeletion;
} else {
deletions.push(currentFallbackChildFragment);
}
}
workInProgress.child = primaryChildFragment;
return primaryChildFragment;
}
function updateSuspenseFallbackChildren(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const currentPrimaryChildFragment: Fiber = (current.child: any);
const currentFallbackChildFragment: Fiber | null =
currentPrimaryChildFragment.sibling;
const primaryChildProps: OffscreenProps = {
mode: 'hidden',
children: primaryChildren,
};
let primaryChildFragment;
if (
// In legacy mode, we commit the primary tree as if it successfully
// completed, even though it's in an inconsistent state.
(mode & ConcurrentMode) === NoMode &&
// Make sure we're on the second pass, i.e. the primary child fragment was
// already cloned. In legacy mode, the only case where this isn't true is
// when DevTools forces us to display a fallback; we skip the first render
// pass entirely and go straight to rendering the fallback. (In Concurrent
// Mode, SuspenseList can also trigger this scenario, but this is a legacy-
// only codepath.)
workInProgress.child !== currentPrimaryChildFragment
) {
const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
primaryChildFragment = progressedPrimaryFragment;
primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// Reset the durations from the first pass so they aren't included in the
// final amounts. This seems counterintuitive, since we're intentionally
// not measuring part of the render phase, but this makes it match what we
// do in Concurrent Mode.
primaryChildFragment.actualDuration = 0;
primaryChildFragment.actualStartTime = -1;
primaryChildFragment.selfBaseDuration =
currentPrimaryChildFragment.selfBaseDuration;
primaryChildFragment.treeBaseDuration =
currentPrimaryChildFragment.treeBaseDuration;
}
// The fallback fiber was added as a deletion during the first pass.
// However, since we're going to remain on the fallback, we no longer want
// to delete it.
workInProgress.deletions = null;
} else {
primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentPrimaryChildFragment,
primaryChildProps,
);
// Since we're reusing a current tree, we need to reuse the flags, too.
// (We don't do this in legacy mode, because in legacy mode we don't re-use
// the current tree; see previous branch.)
primaryChildFragment.subtreeFlags =
currentPrimaryChildFragment.subtreeFlags & StaticMask;
}
let fallbackChildFragment;
if (currentFallbackChildFragment !== null) {
fallbackChildFragment = createWorkInProgress(
currentFallbackChildFragment,
fallbackChildren,
);
} else {
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense boundary) already
// mounted but this is a new fiber.
fallbackChildFragment.flags |= Placement;
}
fallbackChildFragment.return = workInProgress;
primaryChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;
return fallbackChildFragment;
}
function retrySuspenseComponentWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
recoverableError: CapturedValue<mixed> | null,
) {
// Falling back to client rendering. Because this has performance
// implications, it's considered a recoverable error, even though the user
// likely won't observe anything wrong with the UI.
//
// The error is passed in as an argument to enforce that every caller provide
// a custom message, or explicitly opt out (currently the only path that opts
// out is legacy mode; every concurrent path provides an error).
if (recoverableError !== null) {
queueHydrationError(recoverableError);
}
// This will add the old fiber to the deletion list
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
// We're now not suspended nor dehydrated.
const nextProps = workInProgress.pendingProps;
const primaryChildren = nextProps.children;
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
renderLanes,
);
// Needs a placement effect because the parent (the Suspense boundary) already
// mounted but this is a new fiber.
primaryChildFragment.flags |= Placement;
workInProgress.memoizedState = null;
return primaryChildFragment;
}
function mountSuspenseFallbackAfterRetryWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const fiberMode = workInProgress.mode;
const primaryChildProps: OffscreenProps = {
mode: 'visible',
children: primaryChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
fiberMode,
NoLanes,
);
const fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
fiberMode,
renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense
// boundary) already mounted but this is a new fiber.
fallbackChildFragment.flags |= Placement;
primaryChildFragment.return = workInProgress;
fallbackChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;
if ((workInProgress.mode & ConcurrentMode) !== NoMode) {
// We will have dropped the effect list which contains the
// deletion. We need to reconcile to delete the current child.
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
}
return fallbackChildFragment;
}
function mountDehydratedSuspenseComponent(
workInProgress: Fiber,
suspenseInstance: SuspenseInstance,
renderLanes: Lanes,
): null | Fiber {
// During the first pass, we'll bail out and not drill into the children.
// Instead, we'll leave the content in place and try to hydrate it later.
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
if (__DEV__) {
console.error(
'Cannot hydrate Suspense in legacy mode. Switch from ' +
'ReactDOM.hydrate(element, container) to ' +
'ReactDOMClient.hydrateRoot(container, <App />)' +
'.render(element) or remove the Suspense components from ' +
'the server rendered components.',
);
}
workInProgress.lanes = laneToLanes(SyncLane);
} else if (isSuspenseInstanceFallback(suspenseInstance)) {
// This is a client-only boundary. Since we won't get any content from the server
// for this, we need to schedule that at a higher priority based on when it would
// have timed out. In theory we could render it in this pass but it would have the
// wrong priority associated with it and will prevent hydration of parent path.
// Instead, we'll leave work left on it to render it in a separate commit.
// TODO This time should be the time at which the server rendered response that is
// a parent to this boundary was displayed. However, since we currently don't have
// a protocol to transfer that time, we'll just estimate it by using the current
// time. This will mean that Suspense timeouts are slightly shifted to later than
// they should be.
// Schedule a normal pri update to render this content.
workInProgress.lanes = laneToLanes(DefaultHydrationLane);
} else {
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
workInProgress.lanes = laneToLanes(OffscreenLane);
}
return null;
}
function updateDehydratedSuspenseComponent(
current: Fiber,
workInProgress: Fiber,
didSuspend: boolean,
nextProps: any,
suspenseInstance: SuspenseInstance,
suspenseState: SuspenseState,
renderLanes: Lanes,
): null | Fiber {
if (!didSuspend) {
// This is the first render pass. Attempt to hydrate.
pushPrimaryTreeSuspenseHandler(workInProgress);
// We should never be hydrating at this point because it is the first pass,
// but after we've already committed once.
warnIfHydrating();
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
null,
);
}
if (isSuspenseInstanceFallback(suspenseInstance)) {
// This boundary is in a permanent fallback state. In this case, we'll never
// get an update and we'll never be able to hydrate the final content. Let's just try the
// client side render instead.
let digest: ?string;
let message, stack;
if (__DEV__) {
({digest, message, stack} =
getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
} else {
({digest} = getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
}
let capturedValue = null;
// TODO: Figure out a better signal than encoding a magic digest value.
if (!enablePostpone || digest !== 'POSTPONE') {
let error;
if (message) {
// eslint-disable-next-line react-internal/prod-error-codes
error = new Error(message);
} else {
error = new Error(
'The server could not finish this Suspense boundary, likely ' +
'due to an error during server rendering. Switched to ' +
'client rendering.',
);
}
(error: any).digest = digest;
capturedValue = createCapturedValue<mixed>(error, digest, stack);
}
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
capturedValue,
);
}
if (
enableLazyContextPropagation &&
// TODO: Factoring is a little weird, since we check this right below, too.
// But don't want to re-arrange the if-else chain until/unless this
// feature lands.
!didReceiveUpdate
) {
// We need to check if any children have context before we decide to bail
// out, so propagate the changes now.
lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
}
// We use lanes to indicate that a child might depend on context, so if
// any context has changed, we need to treat is as if the input might have changed.
const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
if (didReceiveUpdate || hasContextChanged) {
// This boundary has changed since the first render. This means that we are now unable to
// hydrate it. We might still be able to hydrate it using a higher priority lane.
const root = getWorkInProgressRoot();
if (root !== null) {
const attemptHydrationAtLane = getBumpedLaneForHydration(
root,
renderLanes,
);
if (
attemptHydrationAtLane !== NoLane &&
attemptHydrationAtLane !== suspenseState.retryLane
) {
// Intentionally mutating since this render will get interrupted. This
// is one of the very rare times where we mutate the current tree
// during the render phase.
suspenseState.retryLane = attemptHydrationAtLane;
enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
scheduleUpdateOnFiber(root, current, attemptHydrationAtLane);
// Throw a special object that signals to the work loop that it should
// interrupt the current render.
//
// Because we're inside a React-only execution stack, we don't
// strictly need to throw here — we could instead modify some internal
// work loop state. But using an exception means we don't need to
// check for this case on every iteration of the work loop. So doing
// it this way moves the check out of the fast path.
throw SelectiveHydrationException;
} else {
// We have already tried to ping at a higher priority than we're rendering with
// so if we got here, we must have failed to hydrate at those levels. We must
// now give up. Instead, we're going to delete the whole subtree and instead inject
// a new real Suspense boundary to take its place, which may render content
// or fallback. This might suspend for a while and if it does we might still have
// an opportunity to hydrate before this pass commits.
}
}
// If we did not selectively hydrate, we'll continue rendering without
// hydrating. Mark this tree as suspended to prevent it from committing
// outside a transition.
//
// This path should only happen if the hydration lane already suspended.
// Currently, it also happens during sync updates because there is no
// hydration lane for sync updates.
// TODO: We should ideally have a sync hydration lane that we can apply to do
// a pass where we hydrate this subtree in place using the previous Context and then
// reapply the update afterwards.
if (isSuspenseInstancePending(suspenseInstance)) {
// This is a dehydrated suspense instance. We don't need to suspend
// because we're already showing a fallback.
// TODO: The Fizz runtime might still stream in completed HTML, out-of-
// band. Should we fix this? There's a version of this bug that happens
// during client rendering, too. Needs more consideration.
} else {
renderDidSuspendDelayIfPossible();
}
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
null,
);
} else if (isSuspenseInstancePending(suspenseInstance)) {
// This component is still pending more data from the server, so we can't hydrate its
// content. We treat it as if this component suspended itself. It might seem as if
// we could just try to render it client-side instead. However, this will perform a
// lot of unnecessary work and is unlikely to complete since it often will suspend
// on missing data anyway. Additionally, the server might be able to render more
// than we can on the client yet. In that case we'd end up with more fallback states
// on the client than if we just leave it alone. If the server times out or errors
// these should update this boundary to the permanent Fallback state instead.
// Mark it as having captured (i.e. suspended).
workInProgress.flags |= DidCapture;
// Leave the child in place. I.e. the dehydrated fragment.
workInProgress.child = current.child;
// Register a callback to retry this boundary once the server has sent the result.
const retry = retryDehydratedSuspenseBoundary.bind(null, current);
registerSuspenseInstanceRetry(suspenseInstance, retry);
return null;
} else {
// This is the first attempt.
reenterHydrationStateFromDehydratedSuspenseInstance(
workInProgress,
suspenseInstance,
suspenseState.treeContext,
);
const primaryChildren = nextProps.children;
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
renderLanes,
);
// Mark the children as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
primaryChildFragment.flags |= Hydrating;
return primaryChildFragment;
}
} else {
// This is the second render pass. We already attempted to hydrated, but
// something either suspended or errored.
if (workInProgress.flags & ForceClientRender) {
// Something errored during hydration. Try again without hydrating.
pushPrimaryTreeSuspenseHandler(workInProgress);
workInProgress.flags &= ~ForceClientRender;
const capturedValue = createCapturedValue<mixed>(
new Error(
'There was an error while hydrating this Suspense boundary. ' +
'Switched to client rendering.',
),
);
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
capturedValue,
);
} else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
// Something suspended and we should still be in dehydrated mode.
// Leave the existing child in place.
// Push to avoid a mismatch
pushFallbackTreeSuspenseHandler(workInProgress);
workInProgress.child = current.child;
// The dehydrated completion pass expects this flag to be there
// but the normal suspense pass doesn't.
workInProgress.flags |= DidCapture;
return null;
} else {
// Suspended but we should no longer be in dehydrated mode.
// Therefore we now have to render the fallback.
pushFallbackTreeSuspenseHandler(workInProgress);
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
const fallbackChildFragment =
mountSuspenseFallbackAfterRetryWithoutHydrating(
current,
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
}
}
}
function scheduleSuspenseWorkOnFiber(
fiber: Fiber,
renderLanes: Lanes,
propagationRoot: Fiber,
) {
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
}
function propagateSuspenseContextChange(
workInProgress: Fiber,
firstChild: null | Fiber,
renderLanes: Lanes,
): void {
// Mark any Suspense boundaries with fallbacks as having work to do.
// If they were previously forced into fallbacks, they may now be able
// to unblock.
let node = firstChild;
while (node !== null) {
if (node.tag === SuspenseComponent) {
const state: SuspenseState | null = node.memoizedState;
if (state !== null) {
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
}
} else if (node.tag === SuspenseListComponent) {
// If the tail is hidden there might not be an Suspense boundaries
// to schedule work on. In this case we have to schedule it on the
// list itself.
// We don't have to traverse to the children of the list since
// the list will propagate the change when it rerenders.
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
while (node.sibling === null) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
node.sibling.return = node.return;
node = node.sibling;
}
}
function findLastContentRow(firstChild: null | Fiber): null | Fiber {
// This is going to find the last row among these children that is already
// showing content on the screen, as opposed to being in fallback state or
// new. If a row has multiple Suspense boundaries, any of them being in the
// fallback state, counts as the whole row being in a fallback state.
// Note that the "rows" will be workInProgress, but any nested children
// will still be current since we haven't rendered them yet. The mounted
// order may not be the same as the new order. We use the new order.
let row = firstChild;
let lastContentRow: null | Fiber = null;
while (row !== null) {
const currentRow = row.alternate;
// New rows can't be content rows.
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
lastContentRow = row;
}
row = row.sibling;
}
return lastContentRow;
}
type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together' | void;
function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
if (__DEV__) {
if (
revealOrder !== undefined &&
revealOrder !== 'forwards' &&
revealOrder !== 'backwards' &&
revealOrder !== 'together' &&
!didWarnAboutRevealOrder[revealOrder]
) {
didWarnAboutRevealOrder[revealOrder] = true;
if (typeof revealOrder === 'string') {
switch (revealOrder.toLowerCase()) {
case 'together':
case 'forwards':
case 'backwards': {
console.error(
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
'Use lowercase "%s" instead.',
revealOrder,
revealOrder.toLowerCase(),
);
break;
}
case 'forward':
case 'backward': {
console.error(
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
'React uses the -s suffix in the spelling. Use "%ss" instead.',
revealOrder,
revealOrder.toLowerCase(),
);
break;
}
default:
console.error(
'"%s" is not a supported revealOrder on <SuspenseList />. ' +
'Did you mean "together", "forwards" or "backwards"?',
revealOrder,
);
break;
}
} else {
console.error(
'%s is not a supported value for revealOrder on <SuspenseList />. ' +
'Did you mean "together", "forwards" or "backwards"?',
revealOrder,
);
}
}
}
}
function validateTailOptions(
tailMode: SuspenseListTailMode,
revealOrder: SuspenseListRevealOrder,
) {
if (__DEV__) {
if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) {
if (tailMode !== 'collapsed' && tailMode !== 'hidden') {
didWarnAboutTailOptions[tailMode] = true;
console.error(
'"%s" is not a supported value for tail on <SuspenseList />. ' +
'Did you mean "collapsed" or "hidden"?',
tailMode,
);
} else if (revealOrder !== 'forwards' && revealOrder !== 'backwards') {
didWarnAboutTailOptions[tailMode] = true;
console.error(
'<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
'"forwards" or "backwards". ' +
'Did you mean to specify revealOrder="forwards"?',
tailMode,
);
}
}
}
}
function validateSuspenseListNestedChild(childSlot: mixed, index: number) {
if (__DEV__) {
const isAnArray = isArray(childSlot);
const isIterable =
!isAnArray && typeof getIteratorFn(childSlot) === 'function';
if (isAnArray || isIterable) {
const type = isAnArray ? 'array' : 'iterable';
console.error(
'A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' +
'an additional SuspenseList to configure its revealOrder: ' +
'<SuspenseList revealOrder=...> ... ' +
'<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' +
'</SuspenseList>',
type,
index,
type,
);
return false;
}
}
return true;
}
function validateSuspenseListChildren(
children: mixed,
revealOrder: SuspenseListRevealOrder,
) {
if (__DEV__) {
if (
(revealOrder === 'forwards' || revealOrder === 'backwards') &&
children !== undefined &&
children !== null &&
children !== false
) {
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
if (!validateSuspenseListNestedChild(children[i], i)) {
return;
}
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
const childrenIterator = iteratorFn.call(children);
if (childrenIterator) {
let step = childrenIterator.next();
let i = 0;
for (; !step.done; step = childrenIterator.next()) {
if (!validateSuspenseListNestedChild(step.value, i)) {
return;
}
i++;
}
}
} else {
console.error(
'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
'This is not useful since it needs multiple rows. ' +
'Did you mean to pass multiple children or an array?',
revealOrder,
);
}
}
}
}
}
function initSuspenseListRenderState(
workInProgress: Fiber,
isBackwards: boolean,
tail: null | Fiber,
lastContentRow: null | Fiber,
tailMode: SuspenseListTailMode,
): void {
const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;
if (renderState === null) {
workInProgress.memoizedState = ({
isBackwards: isBackwards,
rendering: null,
renderingStartTime: 0,
last: lastContentRow,
tail: tail,
tailMode: tailMode,
}: SuspenseListRenderState);
} else {
// We can reuse the existing object from previous renders.
renderState.isBackwards = isBackwards;
renderState.rendering = null;
renderState.renderingStartTime = 0;
renderState.last = lastContentRow;
renderState.tail = tail;
renderState.tailMode = tailMode;
}
}
// This can end up rendering this component multiple passes.
// The first pass splits the children fibers into two sets. A head and tail.
// We first render the head. If anything is in fallback state, we do another
// pass through beginWork to rerender all children (including the tail) with
// the force suspend context. If the first render didn't have anything in
// in fallback state. Then we render each row in the tail one-by-one.
// That happens in the completeWork phase without going back to beginWork.
function updateSuspenseListComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = workInProgress.pendingProps;
const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
const tailMode: SuspenseListTailMode = nextProps.tail;
const newChildren = nextProps.children;
validateRevealOrder(revealOrder);
validateTailOptions(tailMode, revealOrder);
validateSuspenseListChildren(newChildren, revealOrder);
reconcileChildren(current, workInProgress, newChildren, renderLanes);
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
const shouldForceFallback = hasSuspenseListContext(
suspenseContext,
(ForceSuspenseFallback: SuspenseContext),
);
if (shouldForceFallback) {
suspenseContext = setShallowSuspenseListContext(
suspenseContext,
ForceSuspenseFallback,
);
workInProgress.flags |= DidCapture;
} else {
const didSuspendBefore =
current !== null && (current.flags & DidCapture) !== NoFlags;
if (didSuspendBefore) {
// If we previously forced a fallback, we need to schedule work
// on any nested boundaries to let them know to try to render
// again. This is the same as context updating.
propagateSuspenseContextChange(
workInProgress,
workInProgress.child,
renderLanes,
);
}
suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
}
pushSuspenseListContext(workInProgress, suspenseContext);
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
// In legacy mode, SuspenseList doesn't work so we just
// use make it a noop by treating it as the default revealOrder.
workInProgress.memoizedState = null;
} else {
switch (revealOrder) {
case 'forwards': {
const lastContentRow = findLastContentRow(workInProgress.child);
let tail;
if (lastContentRow === null) {
// The whole list is part of the tail.
// TODO: We could fast path by just rendering the tail now.
tail = workInProgress.child;
workInProgress.child = null;
} else {
// Disconnect the tail rows after the content row.
// We're going to render them separately later.
tail = lastContentRow.sibling;
lastContentRow.sibling = null;
}
initSuspenseListRenderState(
workInProgress,
false, // isBackwards
tail,
lastContentRow,
tailMode,
);
break;
}
case 'backwards': {
// We're going to find the first row that has existing content.
// At the same time we're going to reverse the list of everything
// we pass in the meantime. That's going to be our tail in reverse
// order.
let tail = null;
let row = workInProgress.child;
workInProgress.child = null;
while (row !== null) {
const currentRow = row.alternate;
// New rows can't be content rows.
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
// This is the beginning of the main content.
workInProgress.child = row;
break;
}
const nextRow = row.sibling;
row.sibling = tail;
tail = row;
row = nextRow;
}
// TODO: If workInProgress.child is null, we can continue on the tail immediately.
initSuspenseListRenderState(
workInProgress,
true, // isBackwards
tail,
null, // last
tailMode,
);
break;
}
case 'together': {
initSuspenseListRenderState(
workInProgress,
false, // isBackwards
null, // tail
null, // last
undefined,
);
break;
}
default: {
// The default reveal order is the same as not having
// a boundary.
workInProgress.memoizedState = null;
}
}
}
return workInProgress.child;
}
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const nextChildren = workInProgress.pendingProps;
if (current === null) {
// Portals are special because we don't append the children during mount
// but at commit. Therefore we need to track insertions which the normal
// flow doesn't do during mount. This doesn't happen at the root because
// the root always starts with a "current" with a null child.
// TODO: Consider unifying this with how the root works.
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
let hasWarnedAboutUsingNoValuePropOnContextProvider = false;
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const providerType: ReactProviderType<any> = workInProgress.type;
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
if (__DEV__) {
if (!('value' in newProps)) {
if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
hasWarnedAboutUsingNoValuePropOnContextProvider = true;
console.error(
'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
);
}
}
const providerPropTypes = workInProgress.type.propTypes;
if (providerPropTypes) {
checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider');
}
}
pushProvider(workInProgress, context, newValue);
if (enableLazyContextPropagation) {
// In the lazy propagation implementation, we don't scan for matching
// consumers until something bails out, because until something bails out
// we're going to visit those nodes, anyway. The trade-off is that it shifts
// responsibility to the consumer to track whether something has changed.
} else {
if (oldProps !== null) {
const oldValue = oldProps.value;
if (is(oldValue, newValue)) {
// No change. Bailout early if children are the same.
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
propagateContextChange(workInProgress, context, renderLanes);
}
}
}
const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
let hasWarnedAboutUsingContextAsConsumer = false;
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any> = workInProgress.type;
// The logic below for Context differs depending on PROD or DEV mode. In
// DEV mode, we create a separate object for Context.Consumer that acts
// like a proxy to Context. This proxy object adds unnecessary code in PROD
// so we use the old behaviour (Context.Consumer references Context) to
// reduce size and overhead. The separate object references context via
// a property called "_context", which also gives us the ability to check
// in DEV mode if this property exists or not and warn if it does not.
if (__DEV__) {
if ((context: any)._context === undefined) {
// This may be because it's a Context (rather than a Consumer).
// Or it may be because it's older React where they're the same thing.
// We only want to warn if we're sure it's a new React.
if (context !== context.Consumer) {
if (!hasWarnedAboutUsingContextAsConsumer) {
hasWarnedAboutUsingContextAsConsumer = true;
console.error(
'Rendering <Context> directly is not supported and will be removed in ' +
'a future major release. Did you mean to render <Context.Consumer> instead?',
);
}
}
} else {
context = (context: any)._context;
}
}
const newProps = workInProgress.pendingProps;
const render = newProps.children;
if (__DEV__) {
if (typeof render !== 'function') {
console.error(
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function. A context consumer expects a single child " +
'that is a function. If you did pass a function, make sure there ' +
'is no trailing or leading whitespace around it.',
);
}
}
prepareToReadContext(workInProgress, renderLanes);
const newValue = readContext(context);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
let newChildren;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
newChildren = render(newValue);
setIsRendering(false);
} else {
newChildren = render(newValue);
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
// React DevTools reads this flag.
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
function updateScopeComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
export function markWorkInProgressReceivedUpdate() {
didReceiveUpdate = true;
}
export function checkIfWorkInProgressReceivedUpdate(): boolean {
return didReceiveUpdate;
}
function resetSuspendedCurrentOnMountInLegacyMode(
current: null | Fiber,
workInProgress: Fiber,
) {
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
if (current !== null) {
// A lazy component only mounts if it suspended inside a non-
// concurrent tree, in an inconsistent state. We want to treat it like
// a new mount, even though an empty version of it already committed.
// Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.flags |= Placement;
}
}
}
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
// Reuse previous dependencies
workInProgress.dependencies = current.dependencies;
}
if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
stopProfilerTimerIfRunning(workInProgress);
}
markSkippedUpdateLanes(workInProgress.lanes);
// Check if the children have any pending work.
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
if (enableLazyContextPropagation && current !== null) {
// Before bailing out, check if there are any context changes in
// the children.
lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
return null;
}
} else {
return null;
}
}
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
function remountFiber(
current: Fiber,
oldWorkInProgress: Fiber,
newWorkInProgress: Fiber,
): Fiber | null {
if (__DEV__) {
const returnFiber = oldWorkInProgress.return;
if (returnFiber === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Cannot swap the root fiber.');
}
// Disconnect from the old current.
// It will get deleted.
current.alternate = null;
oldWorkInProgress.alternate = null;
// Connect to the new tree.
newWorkInProgress.index = oldWorkInProgress.index;
newWorkInProgress.sibling = oldWorkInProgress.sibling;
newWorkInProgress.return = oldWorkInProgress.return;
newWorkInProgress.ref = oldWorkInProgress.ref;
// Replace the child/sibling pointers above it.
if (oldWorkInProgress === returnFiber.child) {
returnFiber.child = newWorkInProgress;
} else {
let prevSibling = returnFiber.child;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected parent to have a child.');
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
while (prevSibling.sibling !== oldWorkInProgress) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
prevSibling = prevSibling.sibling;
if (prevSibling === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Expected to find the previous sibling.');
}
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
prevSibling.sibling = newWorkInProgress;
}
// Delete the old fiber and place the new one.
// Since the old fiber is disconnected, we have to schedule it manually.
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [current];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(current);
}
newWorkInProgress.flags |= Placement;
// Restart work from the new fiber.
return newWorkInProgress;
} else {
throw new Error(
'Did not expect this call in production. ' +
'This is a bug in React. Please file an issue.',
);
}
}
function checkScheduledUpdateOrContext(
current: Fiber,
renderLanes: Lanes,
): boolean {
// Before performing an early bailout, we must check if there are pending
// updates or context.
const updateLanes = current.lanes;
if (includesSomeLane(updateLanes, renderLanes)) {
return true;
}
// No pending update, but because context is propagated lazily, we need
// to check for a context change before we bail out.
if (enableLazyContextPropagation) {
const dependencies = current.dependencies;
if (dependencies !== null && checkIfContextChanged(dependencies)) {
return true;
}
}
return false;
}
function attemptEarlyBailoutIfNoScheduledUpdate(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);
if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}
if (enableCache) {
const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
}
resetHydrationState();
break;
case HostSingleton:
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
const context: ReactContext<any> = workInProgress.type._context;
pushProvider(workInProgress, context, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
// Profiler should only call onRender when one of its descendants actually rendered.
const hasChildWork = includesSomeLane(
renderLanes,
workInProgress.childLanes,
);
if (hasChildWork) {
workInProgress.flags |= Update;
}
if (enableProfilerCommitHooks) {
// Reset effect durations for the next eventual effect phase.
// These are reset during render to allow the DevTools commit hook a chance to read them,
const stateNode = workInProgress.stateNode;
stateNode.effectDuration = 0;
stateNode.passiveEffectDuration = 0;
}
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
if (state !== null) {
if (state.dehydrated !== null) {
// We're not going to render the children, so this is just to maintain
// push/pop symmetry
pushPrimaryTreeSuspenseHandler(workInProgress);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a resolved Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.flags |= DidCapture;
// We should never render the children of a dehydrated boundary until we
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
return null;
}
// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildLanes = primaryChildFragment.childLanes;
if (includesSomeLane(renderLanes, primaryChildLanes)) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(current, workInProgress, renderLanes);
} else {
// The primary child fragment does not have pending work marked
// on it
pushPrimaryTreeSuspenseHandler(workInProgress);
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
// Note: We can return `null` here because we already checked
// whether there were nested context consumers, via the call to
// `bailoutOnAlreadyFinishedWork` above.
return null;
}
}
} else {
pushPrimaryTreeSuspenseHandler(workInProgress);
}
break;
}
case SuspenseListComponent: {
const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;
let hasChildWork = includesSomeLane(
renderLanes,
workInProgress.childLanes,
);
if (enableLazyContextPropagation && !hasChildWork) {
// Context changes may not have been propagated yet. We need to do
// that now, before we can decide whether to bail out.
// TODO: We use `childLanes` as a heuristic for whether there is
// remaining work in a few places, including
// `bailoutOnAlreadyFinishedWork` and
// `updateDehydratedSuspenseComponent`. We should maybe extract this
// into a dedicated function.
lazilyPropagateParentContextChanges(
current,
workInProgress,
renderLanes,
);
hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
}
if (didSuspendBefore) {
if (hasChildWork) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
return updateSuspenseListComponent(
current,
workInProgress,
renderLanes,
);
}
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
workInProgress.flags |= DidCapture;
}
// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
const renderState = workInProgress.memoizedState;
if (renderState !== null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
renderState.rendering = null;
renderState.tail = null;
renderState.lastEffect = null;
}
pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
if (hasChildWork) {
break;
} else {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
return null;
}
}
case OffscreenComponent:
case LegacyHiddenComponent: {
// Need to check if the tree still needs to be deferred. This is
// almost identical to the logic used in the normal update path,
// so we'll just enter that. The only difference is we'll bail out
// at the next level instead of this one, because the child props
// have not changed. Which is fine.
// TODO: Probably should refactor `beginWork` to split the bailout
// path from the normal path. I'm tempted to do a labeled break here
// but I won't :)
workInProgress.lanes = NoLanes;
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case CacheComponent: {
if (enableCache) {
const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
}
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
const instance: TracingMarkerInstance | null = workInProgress.stateNode;
if (instance !== null) {
pushMarkerInstance(workInProgress, instance);
}
}
}
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (__DEV__) {
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
return remountFiber(
current,
workInProgress,
createFiberFromTypeAndProps(
workInProgress.type,
workInProgress.key,
workInProgress.pendingProps,
workInProgress._debugSource || null,
workInProgress._debugOwner || null,
workInProgress.mode,
workInProgress.lanes,
),
);
}
}
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
// Check if this child belongs to a list of muliple children in
// its parent.
//
// In a true multi-threaded implementation, we would render children on
// parallel threads. This would represent the beginning of a new render
// thread for this subtree.
//
// We only use this for id generation during hydration, which is why the
// logic is located in this special branch.
const slotIndex = workInProgress.index;
const numberOfForks = getForksAtLevel(workInProgress);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
// Before entering the begin phase, clear pending update priority.
// TODO: This assumes that we're about to evaluate the component and process
// the update queue. However, there's an exception: SimpleMemoComponent
// sometimes bails out later in the begin phase. This indicates that we should
// move this assignment out of the common path and into each branch.
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostHoistable:
if (enableFloat && supportsResources) {
return updateHostHoistable(current, workInProgress, renderLanes);
}
// Fall through
case HostSingleton:
if (enableHostSingletons && supportsSingletons) {
return updateHostSingleton(current, workInProgress, renderLanes);
}
// Fall through
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentNameFromType(type),
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
case OffscreenComponent: {
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
return updateLegacyHiddenComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case CacheComponent: {
if (enableCache) {
return updateCacheComponent(current, workInProgress, renderLanes);
}
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
return updateTracingMarkerComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.',
);
}
export {beginWork};