/** * Copyright (c) Facebook, Inc. and its 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} from 'shared/ReactTypes'; import type {BlockComponent} from 'react/src/ReactBlock'; import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy'; import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane'; import type {MutableSource} from 'shared/ReactTypes'; import type { SuspenseState, SuspenseListRenderState, SuspenseListTailMode, } from './ReactFiberSuspenseComponent.old'; import type {SuspenseContext} from './ReactFiberSuspenseContext.old'; import type { OffscreenProps, OffscreenState, } from './ReactFiberOffscreenComponent'; import checkPropTypes from 'shared/checkPropTypes'; import { IndeterminateComponent, FunctionComponent, ClassComponent, HostRoot, HostComponent, HostText, HostPortal, ForwardRef, Fragment, Mode, ContextProvider, ContextConsumer, Profiler, SuspenseComponent, SuspenseListComponent, MemoComponent, SimpleMemoComponent, LazyComponent, IncompleteClassComponent, FundamentalComponent, ScopeComponent, Block, OffscreenComponent, LegacyHiddenComponent, } from './ReactWorkTags'; import { NoEffect, PerformedWork, Placement, Hydrating, ContentReset, DidCapture, Update, Ref, Deletion, ForceUpdateForLegacySuspense, } from './ReactSideEffectTags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { debugRenderPhaseSideEffectsForStrictMode, disableLegacyContext, disableModulePatternComponents, enableProfilerTimer, enableSchedulerTracing, enableSuspenseServerRenderer, enableFundamentalAPI, warnAboutDefaultPropsOnFunctionComponents, enableScopeAPI, enableBlocksAPI, } from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import shallowEqual from 'shared/shallowEqual'; import getComponentName from 'shared/getComponentName'; import ReactStrictModeWarnings from './ReactStrictModeWarnings.old'; import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols'; import { getCurrentFiberOwnerNameInDevOrNull, setIsRendering, } from './ReactCurrentFiber'; import { resolveFunctionForHotReloading, resolveForwardRefForHotReloading, resolveClassForHotReloading, } from './ReactFiberHotReloading.old'; import { mountChildFibers, reconcileChildFibers, cloneChildFibers, } from './ReactChildFiber.old'; import { processUpdateQueue, cloneUpdateQueue, initializeUpdateQueue, } from './ReactUpdateQueue.old'; import { NoLane, NoLanes, SyncLane, OffscreenLane, DefaultHydrationLane, NoTimestamp, includesSomeLane, laneToLanes, removeLanes, mergeLanes, getBumpedLaneForHydration, } from './ReactFiberLane'; import { ConcurrentMode, NoMode, ProfileMode, StrictMode, BlockingMode, } from './ReactTypeOfMode'; import { shouldSetTextContent, isSuspenseInstancePending, isSuspenseInstanceFallback, registerSuspenseInstanceRetry, supportsHydration, } from './ReactFiberHostConfig'; import type {SuspenseInstance} from './ReactFiberHostConfig'; import {shouldSuspend} from './ReactFiberReconciler'; import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old'; import { suspenseStackCursor, pushSuspenseContext, InvisibleParentSuspenseContext, ForceSuspenseFallback, hasSuspenseContext, setDefaultShallowSuspenseContext, addSubtreeSuspenseContext, setShallowSuspenseContext, } from './ReactFiberSuspenseContext.old'; import {findFirstSuspended} from './ReactFiberSuspenseComponent.old'; import { pushProvider, propagateContextChange, readContext, prepareToReadContext, calculateChangedBits, scheduleWorkOnParentPath, } from './ReactFiberNewContext.old'; import {renderWithHooks, bailoutHooks} from './ReactFiberHooks.old'; import {stopProfilerTimerIfRunning} from './ReactProfilerTimer.old'; import { getMaskedContext, getUnmaskedContext, hasContextChanged as hasLegacyContextChanged, pushContextProvider as pushLegacyContextProvider, isContextProvider as isLegacyContextProvider, pushTopLevelContextObject, invalidateContextProvider, } from './ReactFiberContext.old'; import { enterHydrationState, reenterHydrationStateFromDehydratedSuspenseInstance, resetHydrationState, tryToClaimNextHydratableInstance, warnIfHydrating, } from './ReactFiberHydrationContext.old'; import { adoptClassInstance, applyDerivedStateFromProps, constructClassInstance, mountClassInstance, resumeMountClassInstance, updateClassInstance, } from './ReactFiberClassComponent.old'; import {resolveDefaultProps} from './ReactFiberLazyComponent.old'; import { resolveLazyComponentTag, createFiberFromTypeAndProps, createFiberFromFragment, createFiberFromOffscreen, createWorkInProgress, isSimpleFunctionComponent, } from './ReactFiber.old'; import { markSpawnedWork, retryDehydratedSuspenseBoundary, scheduleUpdateOnFiber, renderDidSuspendDelayIfPossible, markSkippedUpdateLanes, getWorkInProgressRoot, pushRenderLanes, getExecutionContext, RetryAfterError, NoContext, } from './ReactFiberWorkLoop.old'; import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import {setWorkInProgressVersion} from './ReactMutableSource.old'; import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; let didWarnAboutModulePatternComponent; let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; export let didWarnAboutReassigningProps; let didWarnAboutRevealOrder; let didWarnAboutTailOptions; let didWarnAboutDefaultPropsOnFunctionComponent; if (__DEV__) { didWarnAboutBadClass = {}; didWarnAboutModulePatternComponent = {}; didWarnAboutContextTypeOnFunctionComponent = {}; didWarnAboutGetDerivedStateOnFunctionComponent = {}; didWarnAboutFunctionRefs = {}; didWarnAboutReassigningProps = false; didWarnAboutRevealOrder = {}; didWarnAboutTailOptions = {}; didWarnAboutDefaultPropsOnFunctionComponent = {}; } 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', getComponentName(Component), ); } } } const render = Component.render; const ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent let nextChildren; prepareToReadContext(workInProgress, renderLanes); if (__DEV__) { ReactCurrentOwner.current = workInProgress; setIsRendering(true); nextChildren = renderWithHooks( current, workInProgress, render, nextProps, ref, renderLanes, ); if ( debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode ) { disableLogs(); try { nextChildren = renderWithHooks( current, workInProgress, render, nextProps, ref, renderLanes, ); } finally { reenableLogs(); } } setIsRendering(false); } else { nextChildren = renderWithHooks( current, workInProgress, render, nextProps, ref, renderLanes, ); } if (current !== null && !didReceiveUpdate) { bailoutHooks(current, workInProgress, renderLanes); return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } function updateMemoComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, updateLanes: Lanes, 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, updateLanes, 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', getComponentName(type), ); } } const child = createFiberFromTypeAndProps( Component.type, null, nextProps, null, 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', getComponentName(type), ); } } const currentChild = ((current.child: any): Fiber); // This is always exactly one child if (!includesSomeLane(updateLanes, renderLanes)) { // 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.effectTag |= 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, updateLanes: Lanes, 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 = 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', getComponentName(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; if (!includesSomeLane(renderLanes, updateLanes)) { // 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 accumlated 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.effectTag & ForceUpdateForLegacySuspense) !== NoEffect ) { // 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 prevState: OffscreenState | null = current !== null ? current.memoizedState : null; if ( nextProps.mode === 'hidden' || nextProps.mode === 'unstable-defer-without-hiding' ) { if ((workInProgress.mode & ConcurrentMode) === NoMode) { // In legacy sync mode, don't defer the subtree. Render it now. // TODO: Figure out what we should do in Blocking mode. const nextState: OffscreenState = { baseLanes: NoLanes, }; workInProgress.memoizedState = nextState; pushRenderLanes(workInProgress, renderLanes); } else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) { let nextBaseLanes; if (prevState !== null) { const prevBaseLanes = prevState.baseLanes; nextBaseLanes = mergeLanes(prevBaseLanes, renderLanes); } else { nextBaseLanes = renderLanes; } // Schedule this fiber to re-render at offscreen priority. Then bailout. if (enableSchedulerTracing) { markSpawnedWork((OffscreenLane: Lane)); } workInProgress.lanes = workInProgress.childLanes = laneToLanes( OffscreenLane, ); const nextState: OffscreenState = { baseLanes: nextBaseLanes, }; workInProgress.memoizedState = nextState; // We're about to bail out, but we need to push this to the stack anyway // to avoid a push/pop misalignment. pushRenderLanes(workInProgress, nextBaseLanes); return null; } else { // Rendering at offscreen, so we can clear the base lanes. const nextState: OffscreenState = { baseLanes: NoLanes, }; workInProgress.memoizedState = nextState; // Push the lanes that were skipped when we bailed out. const subtreeRenderLanes = prevState !== null ? prevState.baseLanes : renderLanes; pushRenderLanes(workInProgress, subtreeRenderLanes); } } else { let subtreeRenderLanes; if (prevState !== null) { subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes); // 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. subtreeRenderLanes = renderLanes; } pushRenderLanes(workInProgress, subtreeRenderLanes); } reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } // 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 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.effectTag |= Update; // 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.effectTag |= Ref; } } function updateFunctionComponent( current, workInProgress, Component, nextProps: any, renderLanes, ) { 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', getComponentName(Component), ); } } } let context; if (!disableLegacyContext) { const unmaskedContext = getUnmaskedContext(workInProgress, Component, true); context = getMaskedContext(workInProgress, unmaskedContext); } let nextChildren; prepareToReadContext(workInProgress, renderLanes); if (__DEV__) { ReactCurrentOwner.current = workInProgress; setIsRendering(true); nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes, ); if ( debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode ) { disableLogs(); try { nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes, ); } finally { reenableLogs(); } } setIsRendering(false); } else { nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes, ); } if (current !== null && !didReceiveUpdate) { bailoutHooks(current, workInProgress, renderLanes); return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } function updateBlock( current: Fiber | null, workInProgress: Fiber, block: BlockComponent, 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. const render = block._render; const data = block._data; // The rest is a fork of updateFunctionComponent let nextChildren; prepareToReadContext(workInProgress, renderLanes); if (__DEV__) { ReactCurrentOwner.current = workInProgress; setIsRendering(true); nextChildren = renderWithHooks( current, workInProgress, render, nextProps, data, renderLanes, ); if ( debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode ) { disableLogs(); try { nextChildren = renderWithHooks( current, workInProgress, render, nextProps, data, renderLanes, ); } finally { reenableLogs(); } } setIsRendering(false); } else { nextChildren = renderWithHooks( current, workInProgress, render, nextProps, data, renderLanes, ); } if (current !== null && !didReceiveUpdate) { bailoutHooks(current, workInProgress, renderLanes); return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } function updateClassComponent( current: Fiber | null, 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', getComponentName(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) { if (current !== null) { // A class component without an instance 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.effectTag |= Placement; } // 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.', getComponentName(workInProgress.type) || '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.effectTag & DidCapture) !== NoEffect; 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 (__DEV__) { setIsRendering(true); nextChildren = instance.render(); if ( debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode ) { disableLogs(); try { instance.render(); } finally { reenableLogs(); } } setIsRendering(false); } else { nextChildren = instance.render(); } } // React DevTools reads this flag. workInProgress.effectTag |= 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) { 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, workInProgress, renderLanes) { pushHostRootContext(workInProgress); const updateQueue = workInProgress.updateQueue; invariant( current !== null && updateQueue !== null, 'If the root does not have an updateQueue, we should have already ' + 'bailed out. This error is likely caused by a bug in React. Please ' + 'file an issue.', ); const nextProps = workInProgress.pendingProps; const prevState = workInProgress.memoizedState; const prevChildren = prevState !== null ? prevState.element : null; cloneUpdateQueue(current, workInProgress); processUpdateQueue(workInProgress, nextProps, null, renderLanes); const nextState = workInProgress.memoizedState; // Caution: React DevTools currently depends on this property // being called "element". const nextChildren = nextState.element; if (nextChildren === prevChildren) { resetHydrationState(); return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } const root: FiberRoot = workInProgress.stateNode; if (root.hydrate && enterHydrationState(workInProgress)) { // If we don't have any current children this might be the first pass. // We always try to hydrate. If this isn't a hydration pass there won't // be any children to hydrate which is effectively the same thing as // not hydrating. if (supportsHydration) { const mutableSourceEagerHydrationData = root.mutableSourceEagerHydrationData; if (mutableSourceEagerHydrationData != null) { for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) { const mutableSource = ((mutableSourceEagerHydrationData[ i ]: any): MutableSource); const version = mutableSourceEagerHydrationData[i + 1]; setWorkInProgressVersion(mutableSource, version); } } } 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.effectTag = (node.effectTag & ~Placement) | Hydrating; node = node.sibling; } } else { // Otherwise reset hydration state in case we aborted and resumed another // root. reconcileChildren(current, workInProgress, nextChildren, renderLanes); resetHydrationState(); } 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.effectTag |= ContentReset; } markRef(current, workInProgress); reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } function updateHostText(current, workInProgress) { if (current === null) { tryToClaimNextHydratableInstance(workInProgress); } // Nothing to do here. This is terminal. We'll do the completion step // immediately after. return null; } function mountLazyComponent( _current, workInProgress, elementType, updateLanes, renderLanes, ) { 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.effectTag |= Placement; } const props = workInProgress.pendingProps; const lazyComponent: LazyComponentType = 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', getComponentName(Component), ); } } } child = updateMemoComponent( null, workInProgress, Component, resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too updateLanes, renderLanes, ); return child; } case Block: { if (enableBlocksAPI) { // TODO: Resolve for Hot Reloading. child = updateBlock( null, workInProgress, Component, props, renderLanes, ); return child; } break; } } 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. invariant( false, 'Element type is invalid. Received a promise that resolves to: %s. ' + 'Lazy element type must resolve to a class or function.%s', Component, hint, ); } function mountIncompleteClassComponent( _current, workInProgress, Component, nextProps, renderLanes, ) { if (_current !== null) { // An incomplete 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.effectTag |= Placement; } // 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, workInProgress, Component, renderLanes, ) { if (_current !== null) { // An indeterminate 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.effectTag |= Placement; } const props = workInProgress.pendingProps; let context; if (!disableLegacyContext) { const unmaskedContext = getUnmaskedContext( workInProgress, Component, false, ); context = getMaskedContext(workInProgress, unmaskedContext); } prepareToReadContext(workInProgress, renderLanes); let value; if (__DEV__) { if ( Component.prototype && typeof Component.prototype.render === 'function' ) { const componentName = getComponentName(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 & StrictMode) { ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); } setIsRendering(true); ReactCurrentOwner.current = workInProgress; value = renderWithHooks( null, workInProgress, Component, props, context, renderLanes, ); setIsRendering(false); } else { value = renderWithHooks( null, workInProgress, Component, props, context, renderLanes, ); } // React DevTools reads this flag. workInProgress.effectTag |= 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 = getComponentName(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 = getComponentName(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); const getDerivedStateFromProps = Component.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { applyDerivedStateFromProps( workInProgress, Component, getDerivedStateFromProps, props, ); } 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.', getComponentName(Component) || 'Unknown', ); } if ( debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode ) { disableLogs(); try { value = renderWithHooks( null, workInProgress, Component, props, context, renderLanes, ); } finally { reenableLogs(); } } } 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 || workInProgress._debugID || ''; 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 ( warnAboutDefaultPropsOnFunctionComponents && Component.defaultProps !== undefined ) { const componentName = getComponentName(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 = getComponentName(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 = getComponentName(Component) || 'Unknown'; if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) { console.error( '%s: Function components do not support contextType.', componentName, ); didWarnAboutContextTypeOnFunctionComponent[componentName] = true; } } } } const SUSPENDED_MARKER: SuspenseState = { dehydrated: null, retryLane: NoLane, }; function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState { return { baseLanes: renderLanes, }; } function updateSuspenseOffscreenState( prevOffscreenState: OffscreenState, renderLanes: Lanes, ): OffscreenState { return { baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes), }; } function shouldRemainOnFallback( suspenseContext: SuspenseContext, 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. if (current !== null) { const suspenseState: SuspenseState = current.memoizedState; if (suspenseState === null) { // Currently showing content. Don't hide it, even if ForceSuspenseFallack // 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. return hasSuspenseContext( suspenseContext, (ForceSuspenseFallback: SuspenseContext), ); } function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes) { // TODO: Should not remove render lanes that were pinged during this render return removeLanes(current.childLanes, renderLanes); } function updateSuspenseComponent(current, workInProgress, renderLanes) { const nextProps = workInProgress.pendingProps; // This is used by DevTools to force a boundary to suspend. if (__DEV__) { if (shouldSuspend(workInProgress)) { workInProgress.effectTag |= DidCapture; } } let suspenseContext: SuspenseContext = suspenseStackCursor.current; let showFallback = false; const didSuspend = (workInProgress.effectTag & DidCapture) !== NoEffect; if ( didSuspend || shouldRemainOnFallback( suspenseContext, current, workInProgress, renderLanes, ) ) { // Something in this boundary's subtree already suspended. Switch to // rendering the fallback children. showFallback = true; workInProgress.effectTag &= ~DidCapture; } else { // Attempting the main content if ( current === null || (current.memoizedState: null | SuspenseState) !== null ) { // This is a new mount or this boundary is already showing a fallback state. // Mark this subtree context as having at least one invisible parent that could // handle the fallback state. // Boundaries without fallbacks or should be avoided are not considered since // they cannot handle preferred fallback states. if ( nextProps.fallback !== undefined && nextProps.unstable_avoidThisFallback !== true ) { suspenseContext = addSubtreeSuspenseContext( suspenseContext, InvisibleParentSuspenseContext, ); } } } suspenseContext = setDefaultShallowSuspenseContext(suspenseContext); pushSuspenseContext(workInProgress, suspenseContext); // OK, the next part is confusing. We're about to reconcile the Suspense // boundary's children. This involves some custom reconcilation 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 we're currently hydrating, try to hydrate this boundary. // But only if this has a fallback. if (nextProps.fallback !== undefined) { tryToClaimNextHydratableInstance(workInProgress); // This could've been a dehydrated suspense component. if (enableSuspenseServerRenderer) { const suspenseState: null | SuspenseState = workInProgress.memoizedState; if (suspenseState !== null) { const dehydrated = suspenseState.dehydrated; if (dehydrated !== null) { return mountDehydratedSuspenseComponent( workInProgress, dehydrated, renderLanes, ); } } } } if (showFallback) { const nextPrimaryChildren = nextProps.children; const nextFallbackChildren = nextProps.fallback; const fallbackFragment = mountSuspenseFallbackChildren( workInProgress, nextPrimaryChildren, nextFallbackChildren, renderLanes, ); const primaryChildFragment: Fiber = (workInProgress.child: any); primaryChildFragment.memoizedState = mountSuspenseOffscreenState( renderLanes, ); workInProgress.memoizedState = SUSPENDED_MARKER; return fallbackFragment; } else { const nextPrimaryChildren = nextProps.children; return mountSuspensePrimaryChildren( workInProgress, nextPrimaryChildren, renderLanes, ); } } else { // This is an update. // If the current fiber has a SuspenseState, that means it's already showing // a fallback. const prevState: null | SuspenseState = current.memoizedState; if (prevState !== null) { // The current tree is already showing a fallback // Special path for hydration if (enableSuspenseServerRenderer) { const dehydrated = prevState.dehydrated; if (dehydrated !== null) { if (!didSuspend) { return updateDehydratedSuspenseComponent( current, workInProgress, dehydrated, prevState, renderLanes, ); } else if ( (workInProgress.memoizedState: null | SuspenseState) !== null ) { // Something suspended and we should still be in dehydrated mode. // Leave the existing child in place. workInProgress.child = current.child; // The dehydrated completion pass expects this flag to be there // but the normal suspense pass doesn't. workInProgress.effectTag |= DidCapture; return null; } else { // Suspended but we should no longer be in dehydrated mode. // Therefore we now have to render the fallback. 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; } } } if (showFallback) { 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); primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree( current, renderLanes, ); workInProgress.memoizedState = SUSPENDED_MARKER; return fallbackChildFragment; } else { const nextPrimaryChildren = nextProps.children; const primaryChildFragment = updateSuspensePrimaryChildren( current, workInProgress, nextPrimaryChildren, renderLanes, ); workInProgress.memoizedState = null; return primaryChildFragment; } } else { // The current tree is not already showing a fallback. if (showFallback) { // Timed out. 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); primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree( current, renderLanes, ); // Skip the primary children, and continue working on the // fallback children. workInProgress.memoizedState = SUSPENDED_MARKER; return fallbackChildFragment; } else { // Still haven't timed out. Continue rendering the children, like we // normally do. const nextPrimaryChildren = nextProps.children; const primaryChildFragment = updateSuspensePrimaryChildren( current, workInProgress, nextPrimaryChildren, renderLanes, ); workInProgress.memoizedState = null; return primaryChildFragment; } } } } function mountSuspensePrimaryChildren( workInProgress, primaryChildren, renderLanes, ) { const mode = workInProgress.mode; const primaryChildProps: OffscreenProps = { mode: 'visible', children: primaryChildren, }; const primaryChildFragment = createFiberFromOffscreen( primaryChildProps, mode, renderLanes, null, ); primaryChildFragment.return = workInProgress; workInProgress.child = primaryChildFragment; return primaryChildFragment; } function mountSuspenseFallbackChildren( workInProgress, primaryChildren, fallbackChildren, renderLanes, ) { const mode = workInProgress.mode; const progressedPrimaryFragment: Fiber | null = workInProgress.child; const primaryChildProps: OffscreenProps = { mode: 'hidden', children: primaryChildren, }; let primaryChildFragment; let fallbackChildFragment; if ((mode & BlockingMode) === 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 = createFiberFromOffscreen( primaryChildProps, mode, NoLanes, null, ); fallbackChildFragment = createFiberFromFragment( fallbackChildren, mode, renderLanes, null, ); } primaryChildFragment.return = workInProgress; fallbackChildFragment.return = workInProgress; primaryChildFragment.sibling = fallbackChildFragment; workInProgress.child = primaryChildFragment; return fallbackChildFragment; } function createWorkInProgressOffscreenFiber( 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, workInProgress, primaryChildren, renderLanes, ) { const currentPrimaryChildFragment: Fiber = (current.child: any); const currentFallbackChildFragment: Fiber | null = currentPrimaryChildFragment.sibling; const primaryChildFragment = createWorkInProgressOffscreenFiber( currentPrimaryChildFragment, { mode: 'visible', children: primaryChildren, }, ); if ((workInProgress.mode & BlockingMode) === NoMode) { primaryChildFragment.lanes = renderLanes; } primaryChildFragment.return = workInProgress; primaryChildFragment.sibling = null; if (currentFallbackChildFragment !== null) { // Delete the fallback child fragment currentFallbackChildFragment.nextEffect = null; currentFallbackChildFragment.effectTag = Deletion; workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment; } workInProgress.child = primaryChildFragment; return primaryChildFragment; } function updateSuspenseFallbackChildren( current, workInProgress, primaryChildren, fallbackChildren, renderLanes, ) { 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 & BlockingMode) === 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 effect during the first pass. // However, since we're going to remain on the fallback, we no longer want // to delete it. So we need to remove it from the list. Deletions are stored // on the same list as effects. We want to keep the effects from the primary // tree. So we copy the primary child fragment's effect list, which does not // include the fallback deletion effect. const progressedLastEffect = primaryChildFragment.lastEffect; if (progressedLastEffect !== null) { workInProgress.firstEffect = primaryChildFragment.firstEffect; workInProgress.lastEffect = progressedLastEffect; progressedLastEffect.nextEffect = null; } else { // TODO: Reset this somewhere else? Lol legacy mode is so weird. workInProgress.firstEffect = workInProgress.lastEffect = null; } } else { primaryChildFragment = createWorkInProgressOffscreenFiber( currentPrimaryChildFragment, primaryChildProps, ); } 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.effectTag |= Placement; } fallbackChildFragment.return = workInProgress; primaryChildFragment.return = workInProgress; primaryChildFragment.sibling = fallbackChildFragment; workInProgress.child = primaryChildFragment; return fallbackChildFragment; } function retrySuspenseComponentWithoutHydrating( current: Fiber, workInProgress: Fiber, renderLanes: Lanes, ) { // 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.effectTag |= Placement; workInProgress.memoizedState = null; return primaryChildFragment; } function mountSuspenseFallbackAfterRetryWithoutHydrating( current, workInProgress, primaryChildren, fallbackChildren, renderLanes, ) { const mode = workInProgress.mode; const primaryChildFragment = createFiberFromOffscreen( primaryChildren, mode, NoLanes, null, ); const 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.effectTag |= Placement; primaryChildFragment.return = workInProgress; fallbackChildFragment.return = workInProgress; primaryChildFragment.sibling = fallbackChildFragment; workInProgress.child = primaryChildFragment; if ((workInProgress.mode & BlockingMode) !== 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 & BlockingMode) === NoMode) { if (__DEV__) { console.error( 'Cannot hydrate Suspense in legacy mode. Switch from ' + 'ReactDOM.hydrate(element, container) to ' + 'ReactDOM.createBlockingRoot(container, { hydrate: true })' + '.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. if (enableSchedulerTracing) { markSpawnedWork(DefaultHydrationLane); } 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); if (enableSchedulerTracing) { markSpawnedWork(OffscreenLane); } } return null; } function updateDehydratedSuspenseComponent( current: Fiber, workInProgress: Fiber, suspenseInstance: SuspenseInstance, suspenseState: SuspenseState, renderLanes: Lanes, ): null | Fiber { // We should never be hydrating at this point because it is the first pass, // but after we've already committed once. warnIfHydrating(); if ((getExecutionContext() & RetryAfterError) !== NoContext) { return retrySuspenseComponentWithoutHydrating( current, workInProgress, renderLanes, ); } if ((workInProgress.mode & BlockingMode) === NoMode) { return retrySuspenseComponentWithoutHydrating( current, workInProgress, renderLanes, ); } 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. return retrySuspenseComponentWithoutHydrating( 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; // TODO: Ideally this would inherit the event time of the current render const eventTime = NoTimestamp; scheduleUpdateOnFiber(current, attemptHydrationAtLane, eventTime); } 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 have scheduled higher pri work above, this will probably just abort the render // since we now have higher priority work, but in case it doesn't, we need to prepare to // render something, if we time out. Even if that requires us to delete everything and // skip hydration. // Delay having to do this as long as the suspense timeout allows us. renderDidSuspendDelayIfPossible(); return retrySuspenseComponentWithoutHydrating( current, workInProgress, renderLanes, ); } 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.effectTag |= 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. let retry = retryDehydratedSuspenseBoundary.bind(null, current); if (enableSchedulerTracing) { retry = Schedule_tracing_wrap(retry); } registerSuspenseInstanceRetry(suspenseInstance, retry); return null; } else { // This is the first attempt. reenterHydrationStateFromDehydratedSuspenseInstance( workInProgress, suspenseInstance, ); const nextProps = workInProgress.pendingProps; 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.effectTag |= Hydrating; return primaryChildFragment; } } function scheduleWorkOnFiber(fiber: Fiber, renderLanes: Lanes) { fiber.lanes = mergeLanes(fiber.lanes, renderLanes); const alternate = fiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, renderLanes); } scheduleWorkOnParentPath(fiber.return, renderLanes); } 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) { scheduleWorkOnFiber(node, renderLanes); } } 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. scheduleWorkOnFiber(node, renderLanes); } else if (node.child !== null) { node.child.return = node; node = node.child; continue; } if (node === workInProgress) { return; } while (node.sibling === null) { if (node.return === null || node.return === workInProgress) { return; } node = node.return; } 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 . ' + 'Use lowercase "%s" instead.', revealOrder, revealOrder.toLowerCase(), ); break; } case 'forward': case 'backward': { console.error( '"%s" is not a valid value for revealOrder on . ' + '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 . ' + 'Did you mean "together", "forwards" or "backwards"?', revealOrder, ); break; } } else { console.error( '%s is not a supported value for revealOrder on . ' + '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 . ' + 'Did you mean "collapsed" or "hidden"?', tailMode, ); } else if (revealOrder !== 'forwards' && revealOrder !== 'backwards') { didWarnAboutTailOptions[tailMode] = true; console.error( ' 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 isArray = Array.isArray(childSlot); const isIterable = !isArray && typeof getIteratorFn(childSlot) === 'function'; if (isArray || isIterable) { const type = isArray ? 'array' : 'iterable'; console.error( 'A nested %s was passed to row #%s in . Wrap it in ' + 'an additional SuspenseList to configure its revealOrder: ' + ' ... ' + '{%s} ... ' + '', 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 (Array.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 . ' + '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, lastEffectBeforeRendering: null | Fiber, ): void { const renderState: null | SuspenseListRenderState = workInProgress.memoizedState; if (renderState === null) { workInProgress.memoizedState = ({ isBackwards: isBackwards, rendering: null, renderingStartTime: 0, last: lastContentRow, tail: tail, tailExpiration: 0, tailMode: tailMode, lastEffect: lastEffectBeforeRendering, }: 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.tailExpiration = 0; renderState.tailMode = tailMode; renderState.lastEffect = lastEffectBeforeRendering; } } // 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 = hasSuspenseContext( suspenseContext, (ForceSuspenseFallback: SuspenseContext), ); if (shouldForceFallback) { suspenseContext = setShallowSuspenseContext( suspenseContext, ForceSuspenseFallback, ); workInProgress.effectTag |= DidCapture; } else { const didSuspendBefore = current !== null && (current.effectTag & DidCapture) !== NoEffect; 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 = setDefaultShallowSuspenseContext(suspenseContext); } pushSuspenseContext(workInProgress, suspenseContext); if ((workInProgress.mode & BlockingMode) === 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, workInProgress.lastEffect, ); 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, workInProgress.lastEffect, ); break; } case 'together': { initSuspenseListRenderState( workInProgress, false, // isBackwards null, // tail null, // last undefined, workInProgress.lastEffect, ); 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 = workInProgress.type; const context: ReactContext = 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 ``. Did you misspell it or forget to pass it?', ); } } const providerPropTypes = workInProgress.type.propTypes; if (providerPropTypes) { checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider'); } } pushProvider(workInProgress, newValue); if (oldProps !== null) { const oldValue = oldProps.value; const changedBits = calculateChangedBits(context, newValue, oldValue); if (changedBits === 0) { // 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, changedBits, 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 = 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 directly is not supported and will be removed in ' + 'a future major release. Did you mean to render 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, newProps.unstable_observedBits); let newChildren; if (__DEV__) { ReactCurrentOwner.current = workInProgress; setIsRendering(true); newChildren = render(newValue); setIsRendering(false); } else { newChildren = render(newValue); } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; reconcileChildren(current, workInProgress, newChildren, renderLanes); return workInProgress.child; } function updateFundamentalComponent(current, workInProgress, renderLanes) { const fundamentalImpl = workInProgress.type.impl; if (fundamentalImpl.reconcileChildren === false) { return null; } const nextProps = workInProgress.pendingProps; const nextChildren = nextProps.children; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } function updateScopeComponent(current, workInProgress, renderLanes) { const nextProps = workInProgress.pendingProps; const nextChildren = nextProps.children; reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child; } export function markWorkInProgressReceivedUpdate() { didReceiveUpdate = true; } 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. return null; } else { // 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) { 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) { throw new Error('Expected parent to have a child.'); } while (prevSibling.sibling !== oldWorkInProgress) { prevSibling = prevSibling.sibling; if (prevSibling === null) { throw new Error('Expected to find the previous sibling.'); } } 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 last = returnFiber.lastEffect; if (last !== null) { last.nextEffect = current; returnFiber.lastEffect = current; } else { returnFiber.firstEffect = returnFiber.lastEffect = current; } current.nextEffect = null; current.effectTag = Deletion; newWorkInProgress.effectTag |= 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 beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): Fiber | null { const updateLanes = workInProgress.lanes; 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._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 if (!includesSomeLane(renderLanes, updateLanes)) { didReceiveUpdate = false; // 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); resetHydrationState(); break; 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; pushProvider(workInProgress, 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.effectTag |= Update; } // 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 (enableSuspenseServerRenderer) { if (state.dehydrated !== null) { pushSuspenseContext( workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current), ); // 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.effectTag |= 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 pushSuspenseContext( workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current), ); // 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 { return null; } } } else { pushSuspenseContext( workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current), ); } break; } case SuspenseListComponent: { const didSuspendBefore = (current.effectTag & DidCapture) !== NoEffect; const 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.effectTag |= 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; } pushSuspenseContext(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); } } return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); } else { if ((current.effectTag & ForceUpdateForLegacySuspense) !== NoEffect) { // 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; } // 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, updateLanes, 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 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', getComponentName(type), ); } } } resolvedProps = resolveDefaultProps(type.type, resolvedProps); return updateMemoComponent( current, workInProgress, type, resolvedProps, updateLanes, renderLanes, ); } case SimpleMemoComponent: { return updateSimpleMemoComponent( current, workInProgress, workInProgress.type, workInProgress.pendingProps, updateLanes, 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 FundamentalComponent: { if (enableFundamentalAPI) { return updateFundamentalComponent(current, workInProgress, renderLanes); } break; } case ScopeComponent: { if (enableScopeAPI) { return updateScopeComponent(current, workInProgress, renderLanes); } break; } case Block: { if (enableBlocksAPI) { const block = workInProgress.type; const props = workInProgress.pendingProps; return updateBlock(current, workInProgress, block, props, renderLanes); } break; } case OffscreenComponent: { return updateOffscreenComponent(current, workInProgress, renderLanes); } case LegacyHiddenComponent: { return updateLegacyHiddenComponent(current, workInProgress, renderLanes); } } invariant( false, 'Unknown unit of work tag (%s). This error is likely caused by a bug in ' + 'React. Please file an issue.', workInProgress.tag, ); } export {beginWork};