React Fiber Begin Work
React Fiber Begin Work
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 './ReactFiberOffscreenComponent';
import {OffscreenDetached} from './ReactFiberOffscreenComponent';
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 {
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';
// 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 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});
}
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.
[Link] = reconcileChildFibers(
workInProgress,
[Link],
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.
[Link] = 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 ([Link] !== [Link]) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = [Link];
if (innerPropTypes) {
checkPropTypes(
innerPropTypes,
nextProps, // Resolved props
'prop',
getComponentNameFromType(Component),
);
}
}
}
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
if (current === null) {
const type = [Link];
if (
isSimpleFunctionComponent(type) &&
[Link] === null &&
// SimpleMemoComponent codepath doesn't resolve outer props either.
[Link] === 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.
[Link] = SimpleMemoComponent;
[Link] = resolvedType;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, type);
}
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
if (__DEV__) {
const innerPropTypes = [Link];
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 ([Link] !== undefined) {
const componentName = getComponentNameFromType(type) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
[Link](
'%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(
[Link],
null,
nextProps,
null,
workInProgress,
[Link],
renderLanes,
);
[Link] = [Link];
[Link] = workInProgress;
[Link] = child;
return child;
}
if (__DEV__) {
const type = [Link];
const innerPropTypes = [Link];
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 = (([Link]: any): Fiber); // This is always exactly one
child
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// This will be the props with resolved defaultProps,
// unlike [Link] which will be the unresolved ones.
const prevProps = [Link];
// Default to shallow comparison
let compare = [Link];
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && [Link] === [Link]) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// React DevTools reads this flag.
[Link] |= PerformedWork;
const newChild = createWorkInProgress(currentChild, nextProps);
[Link] = [Link];
[Link] = workInProgress;
[Link] = 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 ([Link] !== [Link]) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
let outerMemoType = [Link];
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 = [Link];
if (
shallowEqual(prevProps, nextProps) &&
[Link] === [Link] &&
// Prevent bailout if the implementation changed due to hot reload.
(__DEV__ ? [Link] === [Link] : 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, [Link]
// 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 [Link], etc) shouldn't
// affect whether the props object is reused during a bailout.
[Link] = 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.
[Link] = [Link];
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else if (([Link] & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See [Link]
didReceiveUpdate = true;
}
}
}
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
function updateOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: OffscreenProps = [Link];
const nextChildren = [Link];
const nextIsDetached =
([Link]._pendingVisibility & OffscreenDetached) !== 0;
markRef(current, workInProgress);
if (
[Link] === 'hidden' ||
(enableLegacyHidden &&
[Link] === 'unstable-defer-without-hiding') ||
nextIsDetached
) {
// Rendering a hidden tree.
// 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, [Link]),
[Link],
);
currentChild = [Link];
}
const lanesWeJustAttempted = nextBaseLanes;
const remainingChildLanes = removeLanes(
currentChildLanes,
lanesWeJustAttempted,
);
[Link] = remainingChildLanes;
} else {
[Link] = NoLanes;
[Link] = null;
}
return deferHiddenOffscreenComponent(
current,
workInProgress,
nextBaseLanes,
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.
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);
}
}
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,
};
[Link] = 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);
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);
// Compare the new parent cache to the previous to see detect there was
// a refresh.
if ([Link] !== parentCache) {
// Refresh in parent. Update the parent.
const derivedState: CacheComponentState = {
parent: parentCache,
cache: parentCache,
};
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 = [Link];
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== [Link]) {
// This cache refreshed. Propagate a context change.
propagateContextChange(workInProgress, CacheContext, renderLanes);
}
}
}
// 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: [Link],
aborts: null,
};
[Link] = 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.
[Link] |= Passive;
}
} else {
if (__DEV__) {
if ([Link] !== [Link]) {
[Link](
'Changing the name of a tracing marker after mount is not supported. ' +
'To remount the tracing marker, pass it a new key.',
);
}
}
}
function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = [Link];
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return [Link];
}
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = [Link];
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return [Link];
}
function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (enableProfilerTimer) {
[Link] |= 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 = [Link];
[Link] = 0;
[Link] = 0;
}
}
const nextProps = [Link];
const nextChildren = [Link];
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return [Link];
}
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
if (__DEV__) {
if ([Link] !== [Link]) {
// Lazy component props can't be validated in createElement
// because they're only guaranteed to be resolved here.
const innerPropTypes = [Link];
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__) {
[Link] = 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();
}
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
const nextChildren = replaySuspendedComponentWithHooks(
current,
workInProgress,
Component,
nextProps,
secondArg,
);
const hasId = checkDidRenderIdHook();
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
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 = [Link];
const ctor = [Link];
// TODO This way of resetting the error boundary state is a hack.
// Is there a better way to do this?
const tempInstance = new ctor(
[Link],
[Link],
);
const state = [Link];
[Link](instance, state, null);
break;
}
case true: {
[Link] |= DidCapture;
[Link] |= ShouldCapture;
// eslint-disable-next-line react-internal/prod-error-codes
const error = new Error('Simulated error coming from DevTools');
const lane = pickArbitraryLane(renderLanes);
[Link] = mergeLanes([Link], lane);
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
createCapturedValueAtFiber(error, workInProgress),
lane,
);
enqueueCapturedUpdate(workInProgress, update);
break;
}
}
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);
// Rerender
[Link] = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof [Link] !== '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 = [Link]();
if (
debugRenderPhaseSideEffectsForStrictMode &&
[Link] & StrictLegacyMode
) {
setIsStrictModeForDevtools(true);
try {
[Link]();
} finally {
setIsStrictModeForDevtools(false);
}
}
setIsRendering(false);
} else {
nextChildren = [Link]();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
}
return [Link];
}
function updateHostRoot(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostRootContext(workInProgress);
if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}
if (enableCache) {
const nextCache: Cache = [Link];
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== [Link]) {
// The root cache refreshed.
propagateContextChange(workInProgress, CacheContext, renderLanes);
}
}
function mountHostRootWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
nextChildren: ReactNodeList,
renderLanes: Lanes,
recoverableError: CapturedValue<mixed>,
) {
// Revert to client rendering.
resetHydrationState();
queueHydrationError(recoverableError);
[Link] |= ForceClientRender;
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);
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.
[Link] |= ContentReset;
}
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return [Link];
}
function updateHostHoistable(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
markRef(current, workInProgress);
const currentProps = current === null ? null : [Link];
const resource = ([Link] = getResource(
[Link],
currentProps,
[Link],
));
if (current === null) {
if (!getIsHydrating() && resource === null) {
// This is not a Resource Hoistable and we aren't hydrating so we construct
the instance.
[Link] = createHoistableInstance(
[Link],
[Link],
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);
function mountLazyComponent(
_current: null | Fiber,
workInProgress: Fiber,
elementType: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
function mountIncompleteClassComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderLanes,
);
}
function mountIndeterminateComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: $FlowFixMe,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
prepareToReadContext(workInProgress, renderLanes);
let value;
let hasId;
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
if (
[Link] &&
typeof [Link] === 'function'
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutBadClass[componentName]) {
[Link](
"The <%s /> component appears to have a render method, but doesn't extend
[Link]. " +
'This is likely to cause errors. Change %s to extend [Link]
instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}
setIsRendering(true);
[Link] = 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();
}
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 [Link] === 'function' &&
value.$$typeof === undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
[Link](
'The <%s /> component appears to be a function component that returns a
class instance. ' +
'Change %s to a class that extends [Link] instead. ' +
"If you can't use a class try assigning the prototype on the function
as a workaround. " +
"`%[Link] = [Link]`. 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 [Link] === 'function' &&
value.$$typeof === undefined
) {
if (__DEV__) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
[Link](
'The <%s /> component appears to be a function component that returns a
class instance. ' +
'Change %s to a class that extends [Link] instead. ' +
"If you can't use a class try assigning the prototype on the function
as a workaround. " +
"`%[Link] = [Link]`. Don't use an arrow
function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
[Link] =
[Link] !== null && [Link] !== undefined ? [Link] : 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
[Link] = FunctionComponent;
if (__DEV__) {
if (disableLegacyContext && [Link]) {
[Link](
'%s uses the legacy contextTypes API which is no longer supported. ' +
'Use [Link]() with [Link]() instead.',
getComponentNameFromType(Component) || 'Unknown',
);
}
}
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
[Link](
'%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 (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
[Link](
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
}
if (
typeof [Link] === 'object' &&
[Link] !== null
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
[Link](
'%s: Function components do not support contextType.',
componentName,
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
}
}
}
const SUSPENDED_MARKER: SuspenseState = {
dehydrated: null,
treeContext: null,
retryLane: NoLane,
};
function updateSuspenseOffscreenState(
prevOffscreenState: OffscreenState,
renderLanes: Lanes,
): OffscreenState {
let cachePool: SpawnedCachePool | null = null;
if (enableCache) {
const prevCachePool: SpawnedCachePool | null = [Link];
if (prevCachePool !== null) {
const parentCache = isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2;
if ([Link] !== 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([Link], renderLanes),
cachePool,
};
}
function updateSuspenseComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = [Link];
// 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
if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);
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 = ([Link]: any);
[Link] =
mountSuspenseOffscreenState(renderLanes);
[Link] = SUSPENDED_MARKER;
if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);
function mountSuspensePrimaryChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = [Link];
const primaryChildProps: OffscreenProps = {
mode: 'visible',
children: primaryChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
renderLanes,
);
[Link] = workInProgress;
[Link] = primaryChildFragment;
return primaryChildFragment;
}
function mountSuspenseFallbackChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = [Link];
const progressedPrimaryFragment: Fiber | null = [Link];
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;
[Link] = NoLanes;
[Link] = primaryChildProps;
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
} else {
primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
NoLanes,
);
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
}
[Link] = workInProgress;
[Link] = workInProgress;
[Link] = fallbackChildFragment;
[Link] = 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 = ([Link]: any);
const currentFallbackChildFragment: Fiber | null =
[Link];
[Link] = primaryChildFragment;
return primaryChildFragment;
}
function updateSuspenseFallbackChildren(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = [Link];
const currentPrimaryChildFragment: Fiber = ([Link]: any);
const currentFallbackChildFragment: Fiber | null =
[Link];
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.)
[Link] !== currentPrimaryChildFragment
) {
const progressedPrimaryFragment: Fiber = ([Link]: any);
primaryChildFragment = progressedPrimaryFragment;
[Link] = NoLanes;
[Link] = primaryChildProps;
// 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.
[Link] = 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.)
[Link] =
[Link] & 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.
[Link] |= Placement;
}
[Link] = workInProgress;
[Link] = workInProgress;
[Link] = fallbackChildFragment;
[Link] = 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);
}
return primaryChildFragment;
}
function mountSuspenseFallbackAfterRetryWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const fiberMode = [Link];
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.
[Link] |= Placement;
[Link] = workInProgress;
[Link] = workInProgress;
[Link] = fallbackChildFragment;
[Link] = primaryChildFragment;
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 (([Link] & ConcurrentMode) === NoMode) {
if (__DEV__) {
[Link](
'Cannot hydrate Suspense in legacy mode. Switch from ' +
'[Link](element, container) to ' +
'[Link](container, <App />)' +
'.render(element) or remove the Suspense components from ' +
'the server rendered components.',
);
}
[Link] = 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.
[Link] = 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.
[Link] = 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);
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));
}
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);
}
// 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.
}
}
[Link] = [Link];
// The dehydrated completion pass expects this flag to be there
// but the normal suspense pass doesn't.
[Link] |= DidCapture;
return null;
} else {
// Suspended but we should no longer be in dehydrated mode.
// Therefore we now have to render the fallback.
pushFallbackTreeSuspenseHandler(workInProgress);
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 ([Link] === SuspenseComponent) {
const state: SuspenseState | null = [Link];
if (state !== null) {
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
}
} else if ([Link] === 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 ([Link] !== null) {
[Link] = node;
node = [Link];
continue;
}
if (node === workInProgress) {
return;
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
while ([Link] === null) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
if ([Link] === null || [Link] === workInProgress) {
return;
}
node = [Link];
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
[Link] = [Link];
node = [Link];
}
}
function validateTailOptions(
tailMode: SuspenseListTailMode,
revealOrder: SuspenseListRevealOrder,
) {
if (__DEV__) {
if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) {
if (tailMode !== 'collapsed' && tailMode !== 'hidden') {
didWarnAboutTailOptions[tailMode] = true;
[Link](
'"%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;
[Link](
'<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
'"forwards" or "backwards". ' +
'Did you mean to specify revealOrder="forwards"?',
tailMode,
);
}
}
}
}
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 < [Link]; i++) {
if (!validateSuspenseListNestedChild(children[i], i)) {
return;
}
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
const childrenIterator = [Link](children);
if (childrenIterator) {
let step = [Link]();
let i = 0;
for (; ![Link]; step = [Link]()) {
if (!validateSuspenseListNestedChild([Link], i)) {
return;
}
i++;
}
}
} else {
[Link](
'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 =
[Link];
if (renderState === null) {
[Link] = ({
isBackwards: isBackwards,
rendering: null,
renderingStartTime: 0,
last: lastContentRow,
tail: tail,
tailMode: tailMode,
}: SuspenseListRenderState);
} else {
// We can reuse the existing object from previous renders.
[Link] = isBackwards;
[Link] = null;
[Link] = 0;
[Link] = lastContentRow;
[Link] = tail;
[Link] = tailMode;
}
}
validateRevealOrder(revealOrder);
validateTailOptions(tailMode, revealOrder);
validateSuspenseListChildren(newChildren, revealOrder);
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContainer(workInProgress, [Link]);
const nextChildren = [Link];
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.
[Link] = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return [Link];
}
if (__DEV__) {
if (!('value' in newProps)) {
if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
hasWarnedAboutUsingNoValuePropOnContextProvider = true;
[Link](
'The `value` prop is required for the `<[Link]>`. Did you
misspell it or forget to pass it?',
);
}
}
const providerPropTypes = [Link];
if (providerPropTypes) {
checkPropTypes(providerPropTypes, newProps, 'prop', '[Link]');
}
}
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 = [Link];
if (is(oldValue, newValue)) {
// No change. Bailout early if children are the same.
if (
[Link] === [Link] &&
!hasLegacyContextChanged()
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
propagateContextChange(workInProgress, context, renderLanes);
}
}
}
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any> = [Link];
// The logic below for Context differs depending on PROD or DEV mode. In
// DEV mode, we create a separate object for [Link] that acts
// like a proxy to Context. This proxy object adds unnecessary code in PROD
// so we use the old behaviour ([Link] 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 !== [Link]) {
if (!hasWarnedAboutUsingContextAsConsumer) {
hasWarnedAboutUsingContextAsConsumer = true;
[Link](
'Rendering <Context> directly is not supported and will be removed in '
+
'a future major release. Did you mean to render <[Link]>
instead?',
);
}
}
} else {
context = (context: any)._context;
}
}
const newProps = [Link];
const render = [Link];
if (__DEV__) {
if (typeof render !== 'function') {
[Link](
'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__) {
[Link] = workInProgress;
setIsRendering(true);
newChildren = render(newValue);
setIsRendering(false);
} else {
newChildren = render(newValue);
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
function updateScopeComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = [Link];
const nextChildren = [Link];
function resetSuspendedCurrentOnMountInLegacyMode(
current: null | Fiber,
workInProgress: Fiber,
) {
if (([Link] & 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.
[Link] = null;
[Link] = null;
// Since this is conceptually a new fiber, schedule a Placement effect
[Link] |= Placement;
}
}
}
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
// Reuse previous dependencies
[Link] = [Link];
}
if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
stopProfilerTimerIfRunning(workInProgress);
}
markSkippedUpdateLanes([Link]);
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
cloneChildFibers(current, workInProgress);
return [Link];
}
function remountFiber(
current: Fiber,
oldWorkInProgress: Fiber,
newWorkInProgress: Fiber,
): Fiber | null {
if (__DEV__) {
const returnFiber = [Link];
if (returnFiber === null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Cannot swap the root fiber.');
}
[Link] |= Placement;
function checkScheduledUpdateOrContext(
current: Fiber,
renderLanes: Lanes,
): boolean {
// Before performing an early bailout, we must check if there are pending
// updates or context.
const updateLanes = [Link];
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 = [Link];
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 ([Link]) {
case HostRoot:
pushHostRootContext(workInProgress);
const root: FiberRoot = [Link];
pushRootTransition(workInProgress, root, renderLanes);
if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}
if (enableCache) {
const cache: Cache = [Link];
pushCacheProvider(workInProgress, cache);
}
resetHydrationState();
break;
case HostSingleton:
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = [Link];
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(workInProgress, [Link]);
break;
case ContextProvider: {
const newValue = [Link];
const context: ReactContext<any> = [Link]._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,
[Link],
);
if (hasChildWork) {
[Link] |= 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 = [Link];
[Link] = 0;
[Link] = 0;
}
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = [Link];
if (state !== null) {
if ([Link] !== 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.
[Link] |= DidCapture;
// We should never render the children of a dehydrated boundary until we
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
return null;
}
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.
[Link] |= DidCapture;
}
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 :)
[Link] = NoLanes;
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case CacheComponent: {
if (enableCache) {
const cache: Cache = [Link];
pushCacheProvider(workInProgress, cache);
}
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
const instance: TracingMarkerInstance | null = [Link];
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(
[Link],
[Link],
[Link],
workInProgress._debugSource || null,
workInProgress._debugOwner || null,
[Link],
[Link],
),
);
}
}
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? [Link] !== [Link] : 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.
([Link] & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if (([Link] & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See [Link]
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;
switch ([Link]) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
[Link],
renderLanes,
);
}
case LazyComponent: {
const elementType = [Link];
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = [Link];
const unresolvedProps = [Link];
const resolvedProps =
[Link] === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = [Link];
const unresolvedProps = [Link];
const resolvedProps =
[Link] === 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 = [Link];
const unresolvedProps = [Link];
const resolvedProps =
[Link] === 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 = [Link];
const unresolvedProps = [Link];
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if ([Link] !== [Link]) {
const outerPropTypes = [Link];
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentNameFromType(type),
);
}
}
}
resolvedProps = resolveDefaultProps([Link], resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
[Link],
[Link],
renderLanes,
);
}
case IncompleteClassComponent: {
const Component = [Link];
const unresolvedProps = [Link];
const resolvedProps =
[Link] === 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;
}
}
export {beginWork};