`. Did you misspell it or forget to pass it?');
- }
- }
- }
+ if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
+ // This is the root of a reappearing boundary. As we continue
+ // traversing the layout effects, we must also re-mount layout
+ // effects that were unmounted when the Offscreen subtree was
+ // hidden. So this is a superset of the normal commitLayoutEffects.
+ var includeWorkInProgressEffects = (finishedWork.subtreeFlags & LayoutMask) !== NoFlags$1;
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
+ } else {
+ recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ }
- pushProvider(workInProgress, context, newValue);
+ offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
+ offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
+ }
+ } else {
+ recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ }
- {
- if (oldProps !== null) {
- var oldValue = oldProps.value;
+ if (flags & Ref) {
+ var props = finishedWork.memoizedProps;
- if (objectIs(oldValue, newValue)) {
- // No change. Bailout early if children are the same.
- if (oldProps.children === newProps.children && !hasContextChanged()) {
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
+ if (props.mode === 'manual') {
+ safelyAttachRef(finishedWork, finishedWork.return);
+ } else {
+ safelyDetachRef(finishedWork, finishedWork.return);
+ }
}
- } else {
- // The context value changed. Search for matching consumers and schedule
- // them to update.
- propagateContextChange(workInProgress, context, renderLanes);
+
+ break;
+ }
+
+ default:
+ {
+ recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ break;
}
- }
}
-
- var newChildren = newProps.children;
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
- return workInProgress.child;
}
-function updateContextConsumer(current, workInProgress, renderLanes) {
- var context;
+function hideOrUnhideAllChildren(finishedWork, isHidden) {
+ // Only hide or unhide the top-most host nodes.
+ var hostSubtreeRoot = null;
{
- var consumerType = workInProgress.type;
- context = consumerType._context;
- }
+ // We only have the top Fiber that was inserted but we need to recurse down its
+ // children to find all the terminal nodes.
+ var node = finishedWork;
- var newProps = workInProgress.pendingProps;
- var render = newProps.children;
+ while (true) {
+ if (node.tag === HostComponent || (node.tag === HostHoistable ) || (node.tag === HostSingleton )) {
+ if (hostSubtreeRoot === null) {
+ hostSubtreeRoot = node;
- {
- if (typeof render !== 'function') {
- 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.');
- }
- }
+ try {
+ var instance = node.stateNode;
- prepareToReadContext(workInProgress, renderLanes);
- var newValue = readContext(context);
+ if (isHidden) {
+ hideInstance(instance);
+ } else {
+ unhideInstance(node.stateNode, node.memoizedProps);
+ }
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ } else if (node.tag === HostText) {
+ if (hostSubtreeRoot === null) {
+ try {
+ var _instance = node.stateNode;
- {
- markComponentRenderStarted(workInProgress);
- }
+ if (isHidden) {
+ hideTextInstance(_instance);
+ } else {
+ unhideTextInstance(_instance, node.memoizedProps);
+ }
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ } else if ((node.tag === OffscreenComponent || node.tag === LegacyHiddenComponent) && node.memoizedState !== null && node !== finishedWork) ; else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
- var newChildren;
+ if (node === finishedWork) {
+ return;
+ }
- {
- setCurrentOwner(workInProgress);
- setIsRendering(true);
- newChildren = render(newValue);
- setIsRendering(false);
- }
+ while (node.sibling === null) {
+ if (node.return === null || node.return === finishedWork) {
+ return;
+ }
- {
- markComponentRenderStopped();
- } // React DevTools reads this flag.
+ if (hostSubtreeRoot === node) {
+ hostSubtreeRoot = null;
+ }
+ node = node.return;
+ }
- workInProgress.flags |= PerformedWork;
- reconcileChildren(current, workInProgress, newChildren, renderLanes);
- return workInProgress.child;
-}
+ if (hostSubtreeRoot === node) {
+ hostSubtreeRoot = null;
+ }
-function markWorkInProgressReceivedUpdate() {
- didReceiveUpdate = true;
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+ }
}
-function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
- if (current !== null) {
- // Reuse previous dependencies
- workInProgress.dependencies = current.dependencies;
- }
+function commitAttachRef(finishedWork) {
+ var ref = finishedWork.ref;
- {
- // Don't update "base" render times for bailouts.
- stopProfilerTimerIfRunning();
- }
+ if (ref !== null) {
+ var instance = finishedWork.stateNode;
+ var instanceToUse;
- markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work.
+ switch (finishedWork.tag) {
+ case HostHoistable:
+ case HostSingleton:
+ case HostComponent:
+ instanceToUse = getPublicInstance(instance);
+ break;
- 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;
- }
- } // This fiber doesn't have work, but its subtree does. Clone the child
- // fibers and continue.
+ default:
+ instanceToUse = instance;
+ } // Moved outside to ensure DCE works with this flag
+ if (typeof ref === 'function') {
+ if (shouldProfile(finishedWork)) {
+ try {
+ startLayoutEffectTimer();
+ finishedWork.refCleanup = ref(instanceToUse);
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ finishedWork.refCleanup = ref(instanceToUse);
+ }
+ } else {
+ {
+ // TODO: We should move these warnings to happen during the render
+ // phase (markRef).
+ if (typeof ref === 'string') {
+ error('String refs are no longer supported.');
+ } else if (!ref.hasOwnProperty('current')) {
+ error('Unexpected ref object provided for %s. ' + 'Use either a ref-setter function or React.createRef().', getComponentNameFromFiber(finishedWork));
+ }
+ } // $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
- cloneChildFibers(current, workInProgress);
- return workInProgress.child;
-}
-function remountFiber(current, oldWorkInProgress, newWorkInProgress) {
- {
- var returnFiber = oldWorkInProgress.return;
+ ref.current = instanceToUse;
+ }
+ }
+}
- if (returnFiber === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Cannot swap the root fiber.');
- } // Disconnect from the old current.
- // It will get deleted.
+function detachFiberMutation(fiber) {
+ // Cut off the return pointer to disconnect it from the tree.
+ // This enables us to detect and warn against state updates on an unmounted component.
+ // It also prevents events from bubbling from within disconnected components.
+ //
+ // Ideally, we should also clear the child pointer of the parent alternate to let this
+ // get GC:ed but we don't know which for sure which parent is the current
+ // one so we'll settle for GC:ing the subtree of this child.
+ // This child itself will be GC:ed when the parent updates the next time.
+ //
+ // Note that we can't clear child or sibling pointers yet.
+ // They're needed for passive effects and for findDOMNode.
+ // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
+ //
+ // Don't reset the alternate yet, either. We need that so we can detach the
+ // alternate's fields in the passive phase. Clearing the return pointer is
+ // sufficient for findDOMNode semantics.
+ var alternate = fiber.alternate;
+ if (alternate !== null) {
+ alternate.return = null;
+ }
- current.alternate = null;
- oldWorkInProgress.alternate = null; // Connect to the new tree.
+ fiber.return = null;
+}
- newWorkInProgress.index = oldWorkInProgress.index;
- newWorkInProgress.sibling = oldWorkInProgress.sibling;
- newWorkInProgress.return = oldWorkInProgress.return;
- newWorkInProgress.ref = oldWorkInProgress.ref;
+function detachFiberAfterEffects(fiber) {
+ var alternate = fiber.alternate;
- {
- newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo;
- } // Replace the child/sibling pointers above it.
+ if (alternate !== null) {
+ fiber.alternate = null;
+ detachFiberAfterEffects(alternate);
+ } // Clear cyclical Fiber fields. This level alone is designed to roughly
+ // approximate the planned Fiber refactor. In that world, `setState` will be
+ // bound to a special "instance" object instead of a Fiber. The Instance
+ // object will not have any of these fields. It will only be connected to
+ // the fiber tree via a single link at the root. So if this level alone is
+ // sufficient to fix memory issues, that bodes well for our plans.
- if (oldWorkInProgress === returnFiber.child) {
- returnFiber.child = newWorkInProgress;
- } else {
- var prevSibling = returnFiber.child;
+ fiber.child = null;
+ fiber.deletions = null;
+ fiber.sibling = null; // The `stateNode` is cyclical because on host nodes it points to the host
+ // tree, which has its own pointers to children, parents, and siblings.
+ // The other host nodes also point back to fibers, so we should detach that
+ // one, too.
- if (prevSibling === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Expected parent to have a child.');
- } // $FlowFixMe[incompatible-use] found when upgrading Flow
+ if (fiber.tag === HostComponent) {
+ var hostInstance = fiber.stateNode;
+ if (hostInstance !== null) {
+ detachDeletedInstance(hostInstance);
+ }
+ }
- while (prevSibling.sibling !== oldWorkInProgress) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- prevSibling = prevSibling.sibling;
+ fiber.stateNode = null;
- if (prevSibling === null) {
- // eslint-disable-next-line react-internal/prod-error-codes
- throw new Error('Expected to find the previous sibling.');
- }
- } // $FlowFixMe[incompatible-use] found when upgrading Flow
+ {
+ fiber._debugOwner = null;
+ } // Theoretically, nothing in here should be necessary, because we already
+ // disconnected the fiber from the tree. So even if something leaks this
+ // particular fiber, it won't leak anything else.
- prevSibling.sibling = newWorkInProgress;
- } // Delete the old fiber and place the new one.
- // Since the old fiber is disconnected, we have to schedule it manually.
+ fiber.return = null;
+ fiber.dependencies = null;
+ fiber.memoizedProps = null;
+ fiber.memoizedState = null;
+ fiber.pendingProps = null;
+ fiber.stateNode = null; // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
+ fiber.updateQueue = null;
+}
- var deletions = returnFiber.deletions;
+function getHostParentFiber(fiber) {
+ var parent = fiber.return;
- if (deletions === null) {
- returnFiber.deletions = [current];
- returnFiber.flags |= ChildDeletion;
- } else {
- deletions.push(current);
+ while (parent !== null) {
+ if (isHostParent(parent)) {
+ return parent;
}
- newWorkInProgress.flags |= Placement; // Restart work from the new fiber.
-
- return newWorkInProgress;
+ parent = parent.return;
}
-}
-
-function checkScheduledUpdateOrContext(current, renderLanes) {
- // Before performing an early bailout, we must check if there are pending
- // updates or context.
- var updateLanes = current.lanes;
- if (includesSomeLane(updateLanes, renderLanes)) {
- return true;
- } // No pending update, but because context is propagated lazily, we need
+ throw new Error('Expected to find a host parent. This error is likely caused by a bug ' + 'in React. Please file an issue.');
+}
- return false;
+function isHostParent(fiber) {
+ return fiber.tag === HostComponent || fiber.tag === HostRoot || (fiber.tag === HostHoistable ) || (fiber.tag === HostSingleton ) || fiber.tag === HostPortal;
}
-function attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes) {
- // 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);
+function getHostSibling(fiber) {
+ // We're going to search forward into the tree until we find a sibling host
+ // node. Unfortunately, if multiple insertions are done in a row we have to
+ // search past them. This leads to exponential search for the next sibling.
+ // TODO: Find a more efficient way to do this.
+ var node = fiber;
- {
- var cache = current.memoizedState.cache;
- pushCacheProvider(workInProgress, cache);
- }
+ siblings: while (true) {
+ // If we didn't find anything, let's try the next sibling.
+ while (node.sibling === null) {
+ if (node.return === null || isHostParent(node.return)) {
+ // If we pop out of the root or hit the parent the fiber we are the
+ // last sibling.
+ return null;
+ } // $FlowFixMe[incompatible-type] found when upgrading Flow
- resetHydrationState();
- break;
- case HostSingleton:
- case HostComponent:
- pushHostContext(workInProgress);
- break;
+ node = node.return;
+ }
- case ClassComponent:
- {
+ node.sibling.return = node.return;
+ node = node.sibling;
- break;
- }
+ while (node.tag !== HostComponent && node.tag !== HostText && (node.tag !== HostSingleton) && node.tag !== DehydratedFragment) {
+ // If it is not host node and, we might have a host node inside it.
+ // Try to search down until we find one.
+ if (node.flags & Placement) {
+ // If we don't have a child, try the siblings instead.
+ continue siblings;
+ } // If we don't have a child, try the siblings instead.
+ // We also skip portals because they are not part of this host tree.
- case HostPortal:
- pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
- break;
- case ContextProvider:
- {
- var newValue = workInProgress.memoizedProps.value;
- var context;
+ if (node.child === null || node.tag === HostPortal) {
+ continue siblings;
+ } else {
+ node.child.return = node;
+ node = node.child;
+ }
+ } // Check if this host node is stable or about to be placed.
- {
- context = workInProgress.type;
- }
- pushProvider(workInProgress, context, newValue);
- break;
- }
+ if (!(node.flags & Placement)) {
+ // Found it!
+ return node.stateNode;
+ }
+ }
+}
- case Profiler:
- {
- // Profiler should only call onRender when one of its descendants actually rendered.
- var hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
+function commitPlacement(finishedWork) {
- if (hasChildWork) {
- workInProgress.flags |= Update;
- }
+ {
+ if (finishedWork.tag === HostSingleton) {
+ // Singletons are already in the Host and don't need to be placed
+ // Since they operate somewhat like Portals though their children will
+ // have Placement and will get placed inside them
+ return;
+ }
+ } // Recursively insert all host nodes into the parent.
- {
- // 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,
- var stateNode = workInProgress.stateNode;
- stateNode.effectDuration = 0;
- stateNode.passiveEffectDuration = 0;
- }
- }
- break;
+ var parentFiber = getHostParentFiber(finishedWork);
- case SuspenseComponent:
+ switch (parentFiber.tag) {
+ case HostSingleton:
{
- var state = workInProgress.memoizedState;
-
- if (state !== null) {
- if (state.dehydrated !== null) {
- // We're not going to render the children, so this is just to maintain
- // push/pop symmetry
- pushPrimaryTreeSuspenseHandler(workInProgress); // We know that this component will suspend again because if it has
- // been unsuspended it has committed as a resolved Suspense component.
- // If it needs to be retried, it should have work scheduled on it.
+ {
+ var parent = parentFiber.stateNode;
+ var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
+ // children to find all the terminal nodes.
- workInProgress.flags |= DidCapture; // We should never render the children of a dehydrated boundary until we
- // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
+ insertOrAppendPlacementNode(finishedWork, before, parent);
+ break;
+ } // Fall through
- 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.
+ }
+ case HostComponent:
+ {
+ var _parent = parentFiber.stateNode;
- var primaryChildFragment = workInProgress.child;
- var primaryChildLanes = primaryChildFragment.childLanes;
+ if (parentFiber.flags & ContentReset) {
+ // Reset the text content of the parent before doing any insertions
+ resetTextContent(_parent); // Clear ContentReset from the effect tag
- if (includesSomeLane(renderLanes, primaryChildLanes)) {
- // The primary children have pending work. Use the normal path
- // to attempt to render the primary children again.
- return updateSuspenseComponent(current, workInProgress, renderLanes);
- } else {
- // The primary child fragment does not have pending work marked
- // on it
- pushPrimaryTreeSuspenseHandler(workInProgress); // The primary children do not have pending work with sufficient
- // priority. Bailout.
+ parentFiber.flags &= ~ContentReset;
+ }
- var child = bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
+ var _before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
+ // children to find all the terminal nodes.
- if (child !== null) {
- // The fallback children have pending work. Skip over the
- // primary children and work on the fallback.
- return child.sibling;
- } else {
- // Note: We can return `null` here because we already checked
- // whether there were nested context consumers, via the call to
- // `bailoutOnAlreadyFinishedWork` above.
- return null;
- }
- }
- } else {
- pushPrimaryTreeSuspenseHandler(workInProgress);
- }
+ insertOrAppendPlacementNode(finishedWork, _before, _parent);
break;
}
- case SuspenseListComponent:
+ case HostRoot:
+ case HostPortal:
{
- var didSuspendBefore = (current.flags & DidCapture) !== NoFlags$1;
-
- var _hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
+ var _parent2 = parentFiber.stateNode.containerInfo;
- 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.
+ var _before2 = getHostSibling(finishedWork);
+ insertOrAppendPlacementNodeIntoContainer(finishedWork, _before2, _parent2);
+ break;
+ }
- workInProgress.flags |= DidCapture;
- } // If nothing suspended before and we're rendering the same children,
- // then the tail doesn't matter. Anything new that suspends will work
- // in the "together" mode, so we can continue from the state we had.
+ default:
+ throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');
+ }
+}
+function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
+ var tag = node.tag;
+ var isHost = tag === HostComponent || tag === HostText;
- var renderState = workInProgress.memoizedState;
+ if (isHost) {
+ var stateNode = node.stateNode;
- 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;
- }
+ if (before) {
+ insertInContainerBefore(parent, stateNode, before);
+ } else {
+ appendChildToContainer(parent, stateNode);
+ }
+ } else if (tag === HostPortal || (tag === HostSingleton )) ; else {
+ var child = node.child;
- pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
+ if (child !== null) {
+ insertOrAppendPlacementNodeIntoContainer(child, before, parent);
+ var sibling = child.sibling;
- 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;
- }
+ while (sibling !== null) {
+ insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
+ sibling = sibling.sibling;
}
+ }
+ }
+}
- 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);
- }
+function insertOrAppendPlacementNode(node, before, parent) {
+ var tag = node.tag;
+ var isHost = tag === HostComponent || tag === HostText;
- case CacheComponent:
- {
- {
- var _cache = current.memoizedState.cache;
- pushCacheProvider(workInProgress, _cache);
- }
+ if (isHost) {
+ var stateNode = node.stateNode;
- break;
+ if (before) {
+ insertBefore(parent, stateNode, before);
+ } else {
+ appendChild(parent, stateNode);
+ }
+ } else if (tag === HostPortal || (tag === HostSingleton )) ; else {
+ var child = node.child;
+
+ if (child !== null) {
+ insertOrAppendPlacementNode(child, before, parent);
+ var sibling = child.sibling;
+
+ while (sibling !== null) {
+ insertOrAppendPlacementNode(sibling, before, parent);
+ sibling = sibling.sibling;
}
+ }
}
+} // These are tracked on the stack as we recursively traverse a
+// deleted subtree.
+// TODO: Update these during the whole mutation phase, not just during
+// a deletion.
- return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
-}
-function beginWork(current, workInProgress, renderLanes) {
+var hostParent = null;
+var hostParentIsContainer = false;
+
+function commitDeletionEffects(root, returnFiber, deletedFiber) {
{
- 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));
- }
- }
+ // We only have the top Fiber that was deleted but we need to recurse down its
+ // children to find all the terminal nodes.
+ // Recursively delete all host nodes from the parent, detach refs, clean
+ // up mounted layout effects, and call componentWillUnmount.
+ // We only need to remove the topmost host child in each branch. But then we
+ // still need to keep traversing to unmount effects, refs, and cWU. TODO: We
+ // could split this into two separate traversals functions, where the second
+ // one doesn't include any removeChild logic. This is maybe the same
+ // function as "disappearLayoutEffects" (or whatever that turns into after
+ // the layout phase is refactored to use recursion).
+ // Before starting, find the nearest host parent on the stack so we know
+ // which instance/container to remove the children from.
+ // TODO: Instead of searching up the fiber return path on every deletion, we
+ // can track the nearest host component on the JS stack as we traverse the
+ // tree during the commit phase. This would make insertions faster, too.
+ var parent = returnFiber;
- if (current !== null) {
- var oldProps = current.memoizedProps;
- var newProps = workInProgress.pendingProps;
+ findParent: while (parent !== null) {
+ switch (parent.tag) {
+ case HostSingleton:
+ case HostComponent:
+ {
+ hostParent = parent.stateNode;
+ hostParentIsContainer = false;
+ break findParent;
+ }
- if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
- workInProgress.type !== current.type )) {
- // If props or context changed, mark the fiber as having performed work.
- // This may be unset if the props are determined to be equal later (memo).
- didReceiveUpdate = true;
- } else {
- // Neither props nor legacy context changes. Check if there's a pending
- // update or context change.
- var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
+ case HostRoot:
+ {
+ hostParent = parent.stateNode.containerInfo;
+ hostParentIsContainer = true;
+ break findParent;
+ }
- if (!hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there
- // may not be work scheduled on `current`, so we check for this flag.
- (workInProgress.flags & DidCapture) === NoFlags$1) {
- // No pending updates or context. Bail out now.
- didReceiveUpdate = false;
- return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
+ case HostPortal:
+ {
+ hostParent = parent.stateNode.containerInfo;
+ hostParentIsContainer = true;
+ break findParent;
+ }
}
- if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags$1) {
- // 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;
- }
+ parent = parent.return;
}
- } else {
- didReceiveUpdate = false;
- if (getIsHydrating() && isForkedChild(workInProgress)) {
- // Check if this child belongs to a list of muliple children in
- // its parent.
- //
- // In a true multi-threaded implementation, we would render children on
- // parallel threads. This would represent the beginning of a new render
- // thread for this subtree.
- //
- // We only use this for id generation during hydration, which is why the
- // logic is located in this special branch.
- var slotIndex = workInProgress.index;
- var numberOfForks = getForksAtLevel();
- pushTreeId(workInProgress, numberOfForks, slotIndex);
+ if (hostParent === null) {
+ throw new Error('Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.');
}
- } // 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.
+ commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
+ hostParent = null;
+ hostParentIsContainer = false;
+ }
- workInProgress.lanes = NoLanes;
+ detachFiberMutation(deletedFiber);
+}
- switch (workInProgress.tag) {
- case LazyComponent:
- {
- var elementType = workInProgress.elementType;
- return mountLazyComponent(current, workInProgress, elementType, renderLanes);
- }
+function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
+ // TODO: Use a static flag to skip trees that don't have unmount effects
+ var child = parent.child;
- case FunctionComponent:
+ while (child !== null) {
+ commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
+ child = child.sibling;
+ }
+}
+
+function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
+ onCommitUnmount(deletedFiber); // The cases in this outer switch modify the stack before they traverse
+ // into their subtree. There are simpler cases in the inner switch
+ // that don't modify the stack.
+
+ switch (deletedFiber.tag) {
+ case HostHoistable:
{
- var Component = workInProgress.type;
- var unresolvedProps = workInProgress.pendingProps;
- var resolvedProps = unresolvedProps ;
- return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
+ {
+ if (!offscreenSubtreeWasHidden) {
+ safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ }
+
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+
+ if (deletedFiber.memoizedState) {
+ releaseResource(deletedFiber.memoizedState);
+ } else if (deletedFiber.stateNode) {
+ unmountHoistable(deletedFiber.stateNode);
+ }
+
+ return;
+ } // Fall through
+
}
- case ClassComponent:
+ case HostSingleton:
{
- var _Component = workInProgress.type;
- var _unresolvedProps = workInProgress.pendingProps;
+ {
+ if (!offscreenSubtreeWasHidden) {
+ safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ }
- var _resolvedProps4 = resolveClassComponentProps(_Component, _unresolvedProps);
+ var prevHostParent = hostParent;
+ var prevHostParentIsContainer = hostParentIsContainer;
+ hostParent = deletedFiber.stateNode;
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); // Normally this is called in passive unmount effect phase however with
+ // HostSingleton we warn if you acquire one that is already associated to
+ // a different fiber. To increase our chances of avoiding this, specifically
+ // if you keyed a HostSingleton so there will be a delete followed by a Placement
+ // we treat detach eagerly here
- return updateClassComponent(current, workInProgress, _Component, _resolvedProps4, renderLanes);
- }
+ releaseSingletonInstance(deletedFiber.stateNode);
+ hostParent = prevHostParent;
+ hostParentIsContainer = prevHostParentIsContainer;
+ return;
+ } // Fall through
- case HostRoot:
- return updateHostRoot(current, workInProgress, renderLanes);
+ }
- case HostHoistable:
+ case HostComponent:
{
- return updateHostHoistable(current, workInProgress);
+ if (!offscreenSubtreeWasHidden) {
+ safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ } // Intentional fallthrough to next branch
+
}
- // Fall through
+ case HostText:
+ {
+ // We only need to remove the nearest host child. Set the host parent
+ // to `null` on the stack to indicate that nested children don't
+ // need to be removed.
+ {
+ var _prevHostParent = hostParent;
+ var _prevHostParentIsContainer = hostParentIsContainer;
+ hostParent = null;
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ hostParent = _prevHostParent;
+ hostParentIsContainer = _prevHostParentIsContainer;
+
+ if (hostParent !== null) {
+ // Now that all the child effects have unmounted, we can remove the
+ // node from the tree.
+ if (hostParentIsContainer) {
+ removeChildFromContainer(hostParent, deletedFiber.stateNode);
+ } else {
+ removeChild(hostParent, deletedFiber.stateNode);
+ }
+ }
+ }
- case HostSingleton:
- {
- return updateHostSingleton(current, workInProgress, renderLanes);
+ return;
}
- // Fall through
+ case DehydratedFragment:
+ {
+ // Delete the dehydrated suspense boundary and all of its content.
- case HostComponent:
- return updateHostComponent$1(current, workInProgress, renderLanes);
- case HostText:
- return updateHostText$1(current, workInProgress);
+ {
+ if (hostParent !== null) {
+ if (hostParentIsContainer) {
+ clearSuspenseBoundaryFromContainer(hostParent, deletedFiber.stateNode);
+ } else {
+ clearSuspenseBoundary(hostParent, deletedFiber.stateNode);
+ }
+ }
+ }
- case SuspenseComponent:
- return updateSuspenseComponent(current, workInProgress, renderLanes);
+ return;
+ }
case HostPortal:
- return updatePortalComponent(current, workInProgress, renderLanes);
-
- case ForwardRef:
{
- var type = workInProgress.type;
- var _unresolvedProps2 = workInProgress.pendingProps;
-
- var _resolvedProps5 = _unresolvedProps2 ;
+ {
+ // When we go into a portal, it becomes the parent to remove from.
+ var _prevHostParent2 = hostParent;
+ var _prevHostParentIsContainer2 = hostParentIsContainer;
+ hostParent = deletedFiber.stateNode.containerInfo;
+ hostParentIsContainer = true;
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ hostParent = _prevHostParent2;
+ hostParentIsContainer = _prevHostParentIsContainer2;
+ }
- return updateForwardRef(current, workInProgress, type, _resolvedProps5, renderLanes);
+ return;
}
- case Fragment:
- return updateFragment(current, workInProgress, renderLanes);
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ {
+ if (!offscreenSubtreeWasHidden) {
+ var updateQueue = deletedFiber.updateQueue;
- case Mode:
- return updateMode(current, workInProgress, renderLanes);
+ if (updateQueue !== null) {
+ var lastEffect = updateQueue.lastEffect;
- case Profiler:
- return updateProfiler(current, workInProgress, renderLanes);
+ if (lastEffect !== null) {
+ var firstEffect = lastEffect.next;
+ var effect = firstEffect;
- case ContextProvider:
- return updateContextProvider(current, workInProgress, renderLanes);
+ do {
+ var tag = effect.tag;
+ var inst = effect.inst;
+ var destroy = inst.destroy;
- case ContextConsumer:
- return updateContextConsumer(current, workInProgress, renderLanes);
+ if (destroy !== undefined) {
+ if ((tag & Insertion) !== NoFlags) {
+ inst.destroy = undefined;
+ safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
+ } else if ((tag & Layout) !== NoFlags) {
+ {
+ markComponentLayoutEffectUnmountStarted(deletedFiber);
+ }
- case MemoComponent:
- {
- var _type = workInProgress.type;
- var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props.
+ if (shouldProfile(deletedFiber)) {
+ startLayoutEffectTimer();
+ inst.destroy = undefined;
+ safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
+ recordLayoutEffectDuration(deletedFiber);
+ } else {
+ inst.destroy = undefined;
+ safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
+ }
- var _resolvedProps6 = _unresolvedProps3 ;
+ {
+ markComponentLayoutEffectUnmountStopped();
+ }
+ }
+ }
- _resolvedProps6 = _resolvedProps6 ;
- return updateMemoComponent(current, workInProgress, _type, _resolvedProps6, renderLanes);
- }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+ }
+ }
- case SimpleMemoComponent:
- {
- return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ return;
}
- case IncompleteClassComponent:
+ case ClassComponent:
{
- {
- break;
- }
- }
+ if (!offscreenSubtreeWasHidden) {
+ safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ var instance = deletedFiber.stateNode;
- case IncompleteFunctionComponent:
- {
- {
- break;
+ if (typeof instance.componentWillUnmount === 'function') {
+ safelyCallComponentWillUnmount(deletedFiber, nearestMountedAncestor, instance);
+ }
}
- }
- case SuspenseListComponent:
- {
- return updateSuspenseListComponent(current, workInProgress, renderLanes);
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ return;
}
case ScopeComponent:
{
- break;
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ return;
}
case OffscreenComponent:
{
- return updateOffscreenComponent(current, workInProgress, renderLanes);
- }
+ safelyDetachRef(deletedFiber, nearestMountedAncestor);
- case LegacyHiddenComponent:
- {
+ {
+ // If this offscreen component is hidden, we already unmounted it. Before
+ // deleting the children, track that it's already unmounted so that we
+ // don't attempt to unmount the effects again.
+ // TODO: If the tree is hidden, in most cases we should be able to skip
+ // over the nested children entirely. An exception is we haven't yet found
+ // the topmost host node to delete, which we already track on the stack.
+ // But the other case is portals, which need to be detached no matter how
+ // deeply they are nested. We should use a subtree flag to track whether a
+ // subtree includes a nested portal.
+ var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
+ offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
+ }
break;
}
- case CacheComponent:
+ default:
{
- {
- return updateCacheComponent(current, workInProgress, renderLanes);
- }
+ recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ return;
}
}
-
- throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
-}
-
-var valueCursor = createCursor(null);
-var rendererCursorDEV;
-
-{
- rendererCursorDEV = createCursor(null);
}
-var rendererSigil;
-
-{
- // Use this to detect multiple renderers using the same context
- rendererSigil = {};
+function commitSuspenseCallback(finishedWork) {
}
-var currentlyRenderingFiber = null;
-var lastContextDependency = null;
-var lastFullyObservedContext = null;
-var isDisallowedContextReadInDEV = false;
-function resetContextDependencies() {
- // This is called right before React yields execution, to ensure `readContext`
- // cannot be called outside the render phase.
- currentlyRenderingFiber = null;
- lastContextDependency = null;
- lastFullyObservedContext = null;
+function commitSuspenseHydrationCallbacks(finishedRoot, finishedWork) {
- {
- isDisallowedContextReadInDEV = false;
- }
-}
-function enterDisallowedContextReadInDEV() {
- {
- isDisallowedContextReadInDEV = true;
- }
-}
-function exitDisallowedContextReadInDEV() {
- {
- isDisallowedContextReadInDEV = false;
- }
-}
-function pushProvider(providerFiber, context, nextValue) {
- {
- push(valueCursor, context._currentValue, providerFiber);
- context._currentValue = nextValue;
+ var newState = finishedWork.memoizedState;
- {
- push(rendererCursorDEV, context._currentRenderer, providerFiber);
+ if (newState === null) {
+ var current = finishedWork.alternate;
- if (context._currentRenderer !== undefined && context._currentRenderer !== null && context._currentRenderer !== rendererSigil) {
- error('Detected multiple renderers concurrently rendering the ' + 'same context provider. This is currently unsupported.');
- }
+ if (current !== null) {
+ var prevState = current.memoizedState;
- context._currentRenderer = rendererSigil;
- }
- }
-}
-function popProvider(context, providerFiber) {
- var currentValue = valueCursor.current;
+ if (prevState !== null) {
+ var suspenseInstance = prevState.dehydrated;
- {
- context._currentValue = currentValue;
+ if (suspenseInstance !== null) {
+ try {
+ commitHydratedSuspenseInstance(suspenseInstance);
- {
- var currentRenderer = rendererCursorDEV.current;
- pop(rendererCursorDEV, providerFiber);
- context._currentRenderer = currentRenderer;
+ var hydrationCallbacks, onHydrated; if (enableSuspenseCallback) ;
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
}
}
-
- pop(valueCursor, providerFiber);
}
-function scheduleContextWorkOnParentPath(parent, renderLanes, propagationRoot) {
- // Update the child lanes of all the ancestors, including the alternates.
- var node = parent;
- while (node !== null) {
- var alternate = node.alternate;
+function getRetryCache(finishedWork) {
+ // TODO: Unify the interface for the retry cache so we don't have to switch
+ // on the tag like this.
+ switch (finishedWork.tag) {
+ case SuspenseComponent:
+ case SuspenseListComponent:
+ {
+ var retryCache = finishedWork.stateNode;
- if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
- node.childLanes = mergeLanes(node.childLanes, renderLanes);
+ if (retryCache === null) {
+ retryCache = finishedWork.stateNode = new PossiblyWeakSet();
+ }
- if (alternate !== null) {
- alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
+ return retryCache;
}
- } else if (alternate !== null && !isSubsetOfLanes(alternate.childLanes, renderLanes)) {
- alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
- } else ;
- if (node === propagationRoot) {
- break;
- }
+ case OffscreenComponent:
+ {
+ var instance = finishedWork.stateNode;
+ var _retryCache = instance._retryCache;
- node = node.return;
- }
+ if (_retryCache === null) {
+ _retryCache = instance._retryCache = new PossiblyWeakSet();
+ }
- {
- if (node !== propagationRoot) {
- error('Expected to find the propagation root when scheduling context work. ' + 'This error is likely caused by a bug in React. Please file an issue.');
- }
- }
-}
-function propagateContextChange(workInProgress, context, renderLanes) {
- {
- propagateContextChange_eager(workInProgress, context, renderLanes);
+ return _retryCache;
+ }
+
+ default:
+ {
+ throw new Error("Unexpected Suspense handler tag (" + finishedWork.tag + "). This is a " + 'bug in React.');
+ }
}
}
-function propagateContextChange_eager(workInProgress, context, renderLanes) {
-
- var fiber = workInProgress.child;
+function detachOffscreenInstance(instance) {
+ var fiber = instance._current;
- if (fiber !== null) {
- // Set the return pointer of the child to the work-in-progress fiber.
- fiber.return = workInProgress;
+ if (fiber === null) {
+ throw new Error('Calling Offscreen.detach before instance handle has been set.');
}
- while (fiber !== null) {
- var nextFiber = void 0; // Visit this fiber.
+ if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags$1) {
+ // The instance is already detached, this is a noop.
+ return;
+ } // TODO: There is an opportunity to optimise this by not entering commit phase
+ // and unmounting effects directly.
- var list = fiber.dependencies;
- if (list !== null) {
- nextFiber = fiber.child;
- var dependency = list.firstContext;
+ var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
- while (dependency !== null) {
- // Check if the context matches.
- if (dependency.context === context) {
- // Match! Schedule an update on this fiber.
- if (fiber.tag === ClassComponent) {
- // Schedule a force update on the work-in-progress.
- var lane = pickArbitraryLane(renderLanes);
- var update = createUpdate(lane);
- update.tag = ForceUpdate; // TODO: Because we don't have a work-in-progress, this will add the
- // update to the current fiber, too, which means it will persist even if
- // this render is thrown away. Since it's a race condition, not sure it's
- // worth fixing.
- // Inlined `enqueueUpdate` to remove interleaved update check
+ if (root !== null) {
+ instance._pendingVisibility |= OffscreenDetached;
+ scheduleUpdateOnFiber(root, fiber, SyncLane);
+ }
+}
+function attachOffscreenInstance(instance) {
+ var fiber = instance._current;
- var updateQueue = fiber.updateQueue;
+ if (fiber === null) {
+ throw new Error('Calling Offscreen.detach before instance handle has been set.');
+ }
- if (updateQueue === null) ; else {
- var sharedQueue = updateQueue.shared;
- var pending = sharedQueue.pending;
+ if ((instance._pendingVisibility & OffscreenDetached) === NoFlags$1) {
+ // The instance is already attached, this is a noop.
+ return;
+ }
- if (pending === null) {
- // This is the first update. Create a circular list.
- update.next = update;
- } else {
- update.next = pending.next;
- pending.next = update;
- }
+ var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
- sharedQueue.pending = update;
- }
- }
+ if (root !== null) {
+ instance._pendingVisibility &= ~OffscreenDetached;
+ scheduleUpdateOnFiber(root, fiber, SyncLane);
+ }
+}
- fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
- var alternate = fiber.alternate;
+function attachSuspenseRetryListeners(finishedWork, wakeables) {
+ // If this boundary just timed out, then it will have a set of wakeables.
+ // For each wakeable, attach a listener so that when it resolves, React
+ // attempts to re-render the boundary in the primary (pre-timeout) state.
+ var retryCache = getRetryCache(finishedWork);
+ wakeables.forEach(function (wakeable) {
+ // Memoize using the boundary fiber to prevent redundant listeners.
+ var retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
- if (alternate !== null) {
- alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
+ if (!retryCache.has(wakeable)) {
+ retryCache.add(wakeable);
+
+ {
+ if (isDevToolsPresent) {
+ if (inProgressLanes !== null && inProgressRoot !== null) {
+ // If we have pending work still, associate the original updaters with it.
+ restorePendingUpdaters(inProgressRoot, inProgressLanes);
+ } else {
+ throw Error('Expected finished root and lanes to be set. This is a bug in React.');
}
+ }
+ }
- scheduleContextWorkOnParentPath(fiber.return, renderLanes, workInProgress); // Mark the updated lanes on the list, too.
+ wakeable.then(retry, retry);
+ }
+ });
+} // This function detects when a Suspense boundary goes from visible to hidden.
+function commitMutationEffects(root, finishedWork, committedLanes) {
+ inProgressLanes = committedLanes;
+ inProgressRoot = root;
+ setCurrentFiber(finishedWork);
+ commitMutationEffectsOnFiber(finishedWork, root);
+ setCurrentFiber(finishedWork);
+ inProgressLanes = null;
+ inProgressRoot = null;
+}
- list.lanes = mergeLanes(list.lanes, renderLanes); // Since we already found a match, we can stop traversing the
- // dependency list.
+function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
+ // Deletions effects can be scheduled on any fiber type. They need to happen
+ // before the children effects hae fired.
+ var deletions = parentFiber.deletions;
- break;
- }
+ if (deletions !== null) {
+ for (var i = 0; i < deletions.length; i++) {
+ var childToDelete = deletions[i];
- dependency = dependency.next;
+ try {
+ commitDeletionEffects(root, parentFiber, childToDelete);
+ } catch (error) {
+ captureCommitPhaseError(childToDelete, parentFiber, error);
}
- } else if (fiber.tag === ContextProvider) {
- // Don't scan deeper if this is a matching provider
- nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
- } else if (fiber.tag === DehydratedFragment) {
- // If a dehydrated suspense boundary is in this subtree, we don't know
- // if it will have any context consumers in it. The best we can do is
- // mark it as having updates.
- var parentSuspense = fiber.return;
+ }
+ }
- if (parentSuspense === null) {
- throw new Error('We just came from a parent so we must have had a parent. This is a bug in React.');
- }
+ var prevDebugFiber = getCurrentFiber();
- parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
- var _alternate = parentSuspense.alternate;
+ if (parentFiber.subtreeFlags & MutationMask) {
+ var child = parentFiber.child;
- if (_alternate !== null) {
- _alternate.lanes = mergeLanes(_alternate.lanes, renderLanes);
- } // This is intentionally passing this fiber as the parent
- // because we want to schedule this fiber as having work
- // on its children. We'll use the childLanes on
- // this fiber to indicate that a context has changed.
+ while (child !== null) {
+ setCurrentFiber(child);
+ commitMutationEffectsOnFiber(child, root);
+ child = child.sibling;
+ }
+ }
+ setCurrentFiber(prevDebugFiber);
+}
- scheduleContextWorkOnParentPath(parentSuspense, renderLanes, workInProgress);
- nextFiber = fiber.sibling;
- } else {
- // Traverse down.
- nextFiber = fiber.child;
- }
+var currentHoistableRoot = null;
- if (nextFiber !== null) {
- // Set the return pointer of the child to the work-in-progress fiber.
- nextFiber.return = fiber;
- } else {
- // No child. Traverse to next sibling.
- nextFiber = fiber;
+function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
+ var current = finishedWork.alternate;
+ var flags = finishedWork.flags; // The effect flag should be checked *after* we refine the type of fiber,
+ // because the fiber tag is more specific. An exception is any flag related
+ // to reconciliation, because those can be set on all fiber types.
- while (nextFiber !== null) {
- if (nextFiber === workInProgress) {
- // We're back to the root of this subtree. Exit.
- nextFiber = null;
- break;
- }
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- var sibling = nextFiber.sibling;
+ if (flags & Update) {
+ try {
+ commitHookEffectListUnmount(Insertion | HasEffect, finishedWork, finishedWork.return);
+ commitHookEffectListMount(Insertion | HasEffect, finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ } // Layout effects are destroyed during the mutation phase so that all
+ // destroy functions for all fibers are called before any create functions.
+ // This prevents sibling component effects from interfering with each other,
+ // e.g. a destroy function in one component should never override a ref set
+ // by a create function in another component during the same commit.
- if (sibling !== null) {
- // Set the return pointer of the sibling to the work-in-progress fiber.
- sibling.return = nextFiber.return;
- nextFiber = sibling;
- break;
- } // No more siblings. Traverse up.
+ if (shouldProfile(finishedWork)) {
+ try {
+ startLayoutEffectTimer();
+ commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+
+ recordLayoutEffectDuration(finishedWork);
+ } else {
+ try {
+ commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
- nextFiber = nextFiber.return;
+ return;
}
- }
- fiber = nextFiber;
- }
-}
-function prepareToReadContext(workInProgress, renderLanes) {
- currentlyRenderingFiber = workInProgress;
- lastContextDependency = null;
- lastFullyObservedContext = null;
- var dependencies = workInProgress.dependencies;
+ case ClassComponent:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- if (dependencies !== null) {
- {
- var firstContext = dependencies.firstContext;
+ if (flags & Ref) {
+ if (current !== null) {
+ safelyDetachRef(current, current.return);
+ }
+ }
- if (firstContext !== null) {
- if (includesSomeLane(dependencies.lanes, renderLanes)) {
- // Context list has a pending update. Mark that this fiber performed work.
- markWorkInProgressReceivedUpdate();
- } // Reset the work-in-progress list
+ if (flags & Callback && offscreenSubtreeIsHidden) {
+ var updateQueue = finishedWork.updateQueue;
+ if (updateQueue !== null) {
+ deferHiddenCallbacks(updateQueue);
+ }
+ }
- dependencies.firstContext = null;
+ return;
}
- }
- }
-}
-function readContext(context) {
- {
- // This warning would fire if you read context inside a Hook like useMemo.
- // Unlike the class check below, it's not enforced in production for perf.
- if (isDisallowedContextReadInDEV) {
- error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().');
- }
- }
- return readContextForConsumer(currentlyRenderingFiber, context);
-}
-function readContextDuringReconciliation(consumer, context, renderLanes) {
- if (currentlyRenderingFiber === null) {
- prepareToReadContext(consumer, renderLanes);
- }
+ case HostHoistable:
+ {
+ {
+ // We cast because we always set the root at the React root and so it cannot be
+ // null while we are processing mutation effects
+ var hoistableRoot = currentHoistableRoot;
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- return readContextForConsumer(consumer, context);
-}
+ if (flags & Ref) {
+ if (current !== null) {
+ safelyDetachRef(current, current.return);
+ }
+ }
-function readContextForConsumer(consumer, context) {
- var value = context._currentValue ;
+ if (flags & Update) {
+ var currentResource = current !== null ? current.memoizedState : null;
+ var newResource = finishedWork.memoizedState;
- if (lastFullyObservedContext === context) ; else {
- var contextItem = {
- context: context,
- memoizedValue: value,
- next: null
- };
+ if (current === null) {
+ // We are mounting a new HostHoistable Fiber. We fork the mount
+ // behavior based on whether this instance is a Hoistable Instance
+ // or a Hoistable Resource
+ if (newResource === null) {
+ if (finishedWork.stateNode === null) {
+ finishedWork.stateNode = hydrateHoistable(hoistableRoot, finishedWork.type, finishedWork.memoizedProps, finishedWork);
+ } else {
+ mountHoistable(hoistableRoot, finishedWork.type, finishedWork.stateNode);
+ }
+ } else {
+ finishedWork.stateNode = acquireResource(hoistableRoot, newResource, finishedWork.memoizedProps);
+ }
+ } else if (currentResource !== newResource) {
+ // We are moving to or from Hoistable Resource, or between different Hoistable Resources
+ if (currentResource === null) {
+ if (current.stateNode !== null) {
+ unmountHoistable(current.stateNode);
+ }
+ } else {
+ releaseResource(currentResource);
+ }
- if (lastContextDependency === null) {
- if (consumer === null) {
- throw new Error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().');
- } // This is the first dependency for this component. Create a new list.
+ if (newResource === null) {
+ mountHoistable(hoistableRoot, finishedWork.type, finishedWork.stateNode);
+ } else {
+ acquireResource(hoistableRoot, newResource, finishedWork.memoizedProps);
+ }
+ } else if (newResource === null && finishedWork.stateNode !== null) {
+ try {
+ commitUpdate(finishedWork.stateNode, finishedWork.type, current.memoizedProps, finishedWork.memoizedProps, finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
+ return;
+ } // Fall through
- lastContextDependency = contextItem;
- consumer.dependencies = {
- lanes: NoLanes,
- firstContext: contextItem
- };
- } else {
- // Append a new context item.
- lastContextDependency = lastContextDependency.next = contextItem;
- }
- }
+ }
- return value;
-}
+ case HostSingleton:
+ {
+ {
+ if (flags & Update) {
+ var previousWork = finishedWork.alternate;
-var UpdateState = 0;
-var ReplaceState = 1;
-var ForceUpdate = 2;
-var CaptureUpdate = 3; // Global state that is reset at the beginning of calling `processUpdateQueue`.
-// It should only be read right after calling `processUpdateQueue`, via
-// `checkHasForceUpdateAfterProcessing`.
+ if (previousWork === null) {
+ var singleton = finishedWork.stateNode;
+ var props = finishedWork.memoizedProps; // This was a new mount, we need to clear and set initial properties
-var hasForceUpdate = false;
-var didWarnUpdateInsideUpdate;
-var currentlyProcessingQueue;
+ clearSingleton(singleton);
+ acquireSingletonInstance(finishedWork.type, props, singleton, finishedWork);
+ }
+ }
+ } // Fall through
-{
- didWarnUpdateInsideUpdate = false;
- currentlyProcessingQueue = null;
-}
+ }
-function initializeUpdateQueue(fiber) {
- var queue = {
- baseState: fiber.memoizedState,
- firstBaseUpdate: null,
- lastBaseUpdate: null,
- shared: {
- pending: null,
- lanes: NoLanes,
- hiddenCallbacks: null
- },
- callbacks: null
- };
- fiber.updateQueue = queue;
-}
-function cloneUpdateQueue(current, workInProgress) {
- // Clone the update queue from current. Unless it's already a clone.
- var queue = workInProgress.updateQueue;
- var currentQueue = current.updateQueue;
+ case HostComponent:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- if (queue === currentQueue) {
- var clone = {
- baseState: currentQueue.baseState,
- firstBaseUpdate: currentQueue.firstBaseUpdate,
- lastBaseUpdate: currentQueue.lastBaseUpdate,
- shared: currentQueue.shared,
- callbacks: null
- };
- workInProgress.updateQueue = clone;
- }
-}
-function createUpdate(lane) {
- var update = {
- lane: lane,
- tag: UpdateState,
- payload: null,
- callback: null,
- next: null
- };
- return update;
-}
-function enqueueUpdate(fiber, update, lane) {
- var updateQueue = fiber.updateQueue;
+ if (flags & Ref) {
+ if (current !== null) {
+ safelyDetachRef(current, current.return);
+ }
+ }
- if (updateQueue === null) {
- // Only occurs if the fiber has been unmounted.
- return null;
- }
+ {
+ // TODO: ContentReset gets cleared by the children during the commit
+ // phase. This is a refactor hazard because it means we must read
+ // flags the flags after `commitReconciliationEffects` has already run;
+ // the order matters. We should refactor so that ContentReset does not
+ // rely on mutating the flag during commit. Like by setting a flag
+ // during the render phase instead.
+ if (finishedWork.flags & ContentReset) {
+ var instance = finishedWork.stateNode;
- var sharedQueue = updateQueue.shared;
+ try {
+ resetTextContent(instance);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
- {
- if (currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate) {
- var componentName = getComponentNameFromFiber(fiber);
+ if (flags & Update) {
+ var _instance2 = finishedWork.stateNode;
+
+ if (_instance2 != null) {
+ // Commit the work prepared earlier.
+ var newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
+ // as the newProps. The updatePayload will contain the real change in
+ // this case.
+
+ var oldProps = current !== null ? current.memoizedProps : newProps;
+ var type = finishedWork.type;
+
+ try {
+ commitUpdate(_instance2, type, oldProps, newProps, finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
- error('An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.\n\nPlease update the following component: %s', componentName);
+ if (flags & FormReset) {
+ needsFormReset = true;
- didWarnUpdateInsideUpdate = true;
- }
- }
+ {
+ if (finishedWork.type !== 'form') {
+ // Paranoid coding. In case we accidentally start using the
+ // FormReset bit for something else.
+ error('Unexpected host component type. Expected a form. This is a ' + 'bug in React.');
+ }
+ }
+ }
+ }
- if (isUnsafeClassRenderPhaseUpdate()) {
- // This is an unsafe render phase update. Add directly to the update
- // queue so we can process it immediately during the current render.
- var pending = sharedQueue.pending;
+ return;
+ }
- if (pending === null) {
- // This is the first update. Create a circular list.
- update.next = update;
- } else {
- update.next = pending.next;
- pending.next = update;
- }
+ case HostText:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- sharedQueue.pending = update; // Update the childLanes even though we're most likely already rendering
- // this fiber. This is for backwards compatibility in the case where you
- // update a different component during render phase than the one that is
- // currently renderings (a pattern that is accompanied by a warning).
+ if (flags & Update) {
+ {
+ if (finishedWork.stateNode === null) {
+ throw new Error('This should have a text node initialized. This error is likely ' + 'caused by a bug in React. Please file an issue.');
+ }
- return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
- } else {
- return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
- }
-}
-function entangleTransitions(root, fiber, lane) {
- var updateQueue = fiber.updateQueue;
+ var textInstance = finishedWork.stateNode;
+ var newText = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
+ // as the newProps. The updatePayload will contain the real change in
+ // this case.
- if (updateQueue === null) {
- // Only occurs if the fiber has been unmounted.
- return;
- }
+ var oldText = current !== null ? current.memoizedProps : newText;
- var sharedQueue = updateQueue.shared;
+ try {
+ commitTextUpdate(textInstance, oldText, newText);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
- if (isTransitionLane(lane)) {
- var queueLanes = sharedQueue.lanes; // If any entangled lanes are no longer pending on the root, then they must
- // have finished. We can remove them from the shared queue, which represents
- // a superset of the actually pending lanes. In some cases we may entangle
- // more than we need to, but that's OK. In fact it's worse if we *don't*
- // entangle when we should.
+ return;
+ }
- queueLanes = intersectLanes(queueLanes, root.pendingLanes); // Entangle the new transition lane with the other transition lanes.
+ case HostRoot:
+ {
+ {
+ prepareToCommitHoistables();
+ var previousHoistableRoot = currentHoistableRoot;
+ currentHoistableRoot = getHoistableRoot(root.containerInfo);
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ currentHoistableRoot = previousHoistableRoot;
+ commitReconciliationEffects(finishedWork);
+ }
- var newQueueLanes = mergeLanes(queueLanes, lane);
- sharedQueue.lanes = newQueueLanes; // Even if queue.lanes already include lane, we don't know for certain if
- // the lane finished since the last time we entangled it. So we need to
- // entangle it again, just to be sure.
+ if (flags & Update) {
+ {
+ if (current !== null) {
+ var prevRootState = current.memoizedState;
- markRootEntangled(root, newQueueLanes);
- }
-}
-function enqueueCapturedUpdate(workInProgress, capturedUpdate) {
- // Captured updates are updates that are thrown by a child during the render
- // phase. They should be discarded if the render is aborted. Therefore,
- // we should only put them on the work-in-progress queue, not the current one.
- var queue = workInProgress.updateQueue; // Check if the work-in-progress queue is a clone.
+ if (prevRootState.isDehydrated) {
+ try {
+ commitHydratedContainer(root.containerInfo);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ }
+ }
+ }
+ }
- var current = workInProgress.alternate;
+ if (needsFormReset) {
+ // A form component requested to be reset during this commit. We do this
+ // after all mutations in the rest of the tree so that `defaultValue`
+ // will already be updated. This way you can update `defaultValue` using
+ // data sent by the server as a result of the form submission.
+ //
+ // Theoretically we could check finishedWork.subtreeFlags & FormReset,
+ // but the FormReset bit is overloaded with other flags used by other
+ // fiber types. So this extra variable lets us skip traversing the tree
+ // except when a form was actually submitted.
+ needsFormReset = false;
+ recursivelyResetForms(finishedWork);
+ }
- if (current !== null) {
- var currentQueue = current.updateQueue;
+ return;
+ }
- if (queue === currentQueue) {
- // The work-in-progress queue is the same as current. This happens when
- // we bail out on a parent fiber that then captures an error thrown by
- // a child. Since we want to append the update only to the work-in
- // -progress queue, we need to clone the updates. We usually clone during
- // processUpdateQueue, but that didn't happen in this case because we
- // skipped over the parent when we bailed out.
- var newFirst = null;
- var newLast = null;
- var firstBaseUpdate = queue.firstBaseUpdate;
+ case HostPortal:
+ {
+ {
+ var _previousHoistableRoot = currentHoistableRoot;
+ currentHoistableRoot = getHoistableRoot(finishedWork.stateNode.containerInfo);
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ currentHoistableRoot = _previousHoistableRoot;
+ }
- if (firstBaseUpdate !== null) {
- // Loop through the updates and clone them.
- var update = firstBaseUpdate;
+ return;
+ }
- do {
- var clone = {
- lane: update.lane,
- tag: update.tag,
- payload: update.payload,
- // When this update is rebased, we should not fire its
- // callback again.
- callback: null,
- next: null
- };
+ case SuspenseComponent:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork); // TODO: We should mark a flag on the Suspense fiber itself, rather than
+ // relying on the Offscreen fiber having a flag also being marked. The
+ // reason is that this offscreen fiber might not be part of the work-in-
+ // progress tree! It could have been reused from a previous render. This
+ // doesn't lead to incorrect behavior because we don't rely on the flag
+ // check alone; we also compare the states explicitly below. But for
+ // modeling purposes, we _should_ be able to rely on the flag check alone.
+ // So this is a bit fragile.
+ //
+ // Also, all this logic could/should move to the passive phase so it
+ // doesn't block paint.
- if (newLast === null) {
- newFirst = newLast = clone;
- } else {
- newLast.next = clone;
- newLast = clone;
- } // $FlowFixMe[incompatible-type] we bail out when we get a null
+ var offscreenFiber = finishedWork.child;
+ if (offscreenFiber.flags & Visibility) {
+ // Throttle the appearance and disappearance of Suspense fallbacks.
+ var isShowingFallback = finishedWork.memoizedState !== null;
+ var wasShowingFallback = current !== null && current.memoizedState !== null;
- update = update.next;
- } while (update !== null); // Append the captured update the end of the cloned list.
+ {
+ if (isShowingFallback !== wasShowingFallback) {
+ // A fallback is either appearing or disappearing.
+ markCommitTimeOfFallback();
+ }
+ }
+ }
+ if (flags & Update) {
+ try {
+ commitSuspenseCallback(finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
- if (newLast === null) {
- newFirst = newLast = capturedUpdate;
- } else {
- newLast.next = capturedUpdate;
- newLast = capturedUpdate;
+ var retryQueue = finishedWork.updateQueue;
+
+ if (retryQueue !== null) {
+ finishedWork.updateQueue = null;
+ attachSuspenseRetryListeners(finishedWork, retryQueue);
+ }
}
- } else {
- // There are no base updates.
- newFirst = newLast = capturedUpdate;
- }
- queue = {
- baseState: currentQueue.baseState,
- firstBaseUpdate: newFirst,
- lastBaseUpdate: newLast,
- shared: currentQueue.shared,
- callbacks: currentQueue.callbacks
- };
- workInProgress.updateQueue = queue;
- return;
- }
- } // Append the update to the end of the list.
+ return;
+ }
+ case OffscreenComponent:
+ {
+ if (flags & Ref) {
+ if (current !== null) {
+ safelyDetachRef(current, current.return);
+ }
+ }
- var lastBaseUpdate = queue.lastBaseUpdate;
+ var newState = finishedWork.memoizedState;
+ var isHidden = newState !== null;
+ var wasHidden = current !== null && current.memoizedState !== null;
- if (lastBaseUpdate === null) {
- queue.firstBaseUpdate = capturedUpdate;
- } else {
- lastBaseUpdate.next = capturedUpdate;
- }
+ {
+ // Before committing the children, track on the stack whether this
+ // offscreen subtree was already hidden, so that we don't unmount the
+ // effects again.
+ var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
+ var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
+ offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
+ offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
+ offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
+ }
- queue.lastBaseUpdate = capturedUpdate;
-}
+ commitReconciliationEffects(finishedWork);
+ var offscreenInstance = finishedWork.stateNode; // TODO: Add explicit effect flag to set _current.
-function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
- switch (update.tag) {
- case ReplaceState:
- {
- var payload = update.payload;
+ offscreenInstance._current = finishedWork; // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
+ // to support batching of `attach` and `detach` calls.
- if (typeof payload === 'function') {
- // Updater function
- {
- enterDisallowedContextReadInDEV();
- }
+ offscreenInstance._visibility &= ~OffscreenDetached;
+ offscreenInstance._visibility |= offscreenInstance._pendingVisibility & OffscreenDetached;
- var nextState = payload.call(instance, prevState, nextProps);
+ if (flags & Visibility) {
+ // Track the current state on the Offscreen instance so we can
+ // read it during an event
+ if (isHidden) {
+ offscreenInstance._visibility &= ~OffscreenVisible;
+ } else {
+ offscreenInstance._visibility |= OffscreenVisible;
+ }
- {
- if (workInProgress.mode & StrictLegacyMode) {
- setIsStrictModeForDevtools(true);
+ if (isHidden) {
+ var isUpdate = current !== null;
+ var wasHiddenByAncestorOffscreen = offscreenSubtreeIsHidden || offscreenSubtreeWasHidden; // Only trigger disapper layout effects if:
+ // - This is an update, not first mount.
+ // - This Offscreen was not hidden before.
+ // - Ancestor Offscreen was not hidden in previous commit.
- try {
- payload.call(instance, prevState, nextProps);
- } finally {
- setIsStrictModeForDevtools(false);
+ if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) {
+ {
+ // Disappear the layout effects of all the children
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
}
}
+ } // Offscreen with manual mode manages visibility manually.
- exitDisallowedContextReadInDEV();
- }
-
- return nextState;
- } // State object
+ if (!isOffscreenManual(finishedWork)) {
+ // TODO: This needs to run whenever there's an insertion or update
+ // inside a hidden Offscreen tree.
+ hideOrUnhideAllChildren(finishedWork, isHidden);
+ }
+ } // TODO: Move to passive phase
- return payload;
- }
- case CaptureUpdate:
- {
- workInProgress.flags = workInProgress.flags & ~ShouldCapture | DidCapture;
- }
- // Intentional fallthrough
+ if (flags & Update) {
+ var offscreenQueue = finishedWork.updateQueue;
- case UpdateState:
- {
- var _payload = update.payload;
- var partialState;
+ if (offscreenQueue !== null) {
+ var _retryQueue = offscreenQueue.retryQueue;
- if (typeof _payload === 'function') {
- // Updater function
- {
- enterDisallowedContextReadInDEV();
+ if (_retryQueue !== null) {
+ offscreenQueue.retryQueue = null;
+ attachSuspenseRetryListeners(finishedWork, _retryQueue);
+ }
}
+ }
- partialState = _payload.call(instance, prevState, nextProps);
+ return;
+ }
- {
- if (workInProgress.mode & StrictLegacyMode) {
- setIsStrictModeForDevtools(true);
+ case SuspenseListComponent:
+ {
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
- try {
- _payload.call(instance, prevState, nextProps);
- } finally {
- setIsStrictModeForDevtools(false);
- }
- }
+ if (flags & Update) {
+ var _retryQueue2 = finishedWork.updateQueue;
- exitDisallowedContextReadInDEV();
+ if (_retryQueue2 !== null) {
+ finishedWork.updateQueue = null;
+ attachSuspenseRetryListeners(finishedWork, _retryQueue2);
}
- } else {
- // Partial state object
- partialState = _payload;
}
- if (partialState === null || partialState === undefined) {
- // Null and undefined are treated as no-ops.
- return prevState;
- } // Merge the partial state and the previous state.
+ return;
+ }
+ case ScopeComponent:
+ {
- return assign({}, prevState, partialState);
+ return;
}
- case ForceUpdate:
+ default:
{
- hasForceUpdate = true;
- return prevState;
+ recursivelyTraverseMutationEffects(root, finishedWork);
+ commitReconciliationEffects(finishedWork);
+ return;
}
}
-
- return prevState;
}
-var didReadFromEntangledAsyncAction = false; // Each call to processUpdateQueue should be accompanied by a call to this. It's
-// only in a separate function because in updateHostRoot, it must happen after
-// all the context stacks have been pushed to, to prevent a stack mismatch. A
-// bit unfortunate.
-
-function suspendIfUpdateReadFromEntangledAsyncAction() {
- // Check if this update is part of a pending async action. If so, we'll
- // need to suspend until the action has finished, so that it's batched
- // together with future updates in the same action.
- // TODO: Once we support hooks inside useMemo (or an equivalent
- // memoization boundary like Forget), hoist this logic so that it only
- // suspends if the memo boundary produces a new value.
- if (didReadFromEntangledAsyncAction) {
- var entangledActionThenable = peekEntangledActionThenable();
+function commitReconciliationEffects(finishedWork) {
+ // Placement effects (insertions, reorders) can be scheduled on any fiber
+ // type. They needs to happen after the children effects have fired, but
+ // before the effects on this fiber have fired.
+ var flags = finishedWork.flags;
- if (entangledActionThenable !== null) {
- // TODO: Instead of the throwing the thenable directly, throw a
- // special object like `use` does so we can detect if it's captured
- // by userspace.
- throw entangledActionThenable;
- }
- }
-}
-function processUpdateQueue(workInProgress, props, instance, renderLanes) {
- didReadFromEntangledAsyncAction = false; // This is always non-null on a ClassComponent or HostRoot
+ if (flags & Placement) {
+ try {
+ commitPlacement(finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ } // Clear the "placement" from effect tag so that we know that this is
+ // inserted, before any life-cycles like componentDidMount gets called.
+ // TODO: findDOMNode doesn't rely on this any more but isMounted does
+ // and isMounted is deprecated anyway so we should be able to kill this.
- var queue = workInProgress.updateQueue;
- hasForceUpdate = false;
- {
- currentlyProcessingQueue = queue.shared;
+ finishedWork.flags &= ~Placement;
}
- var firstBaseUpdate = queue.firstBaseUpdate;
- var lastBaseUpdate = queue.lastBaseUpdate; // Check if there are pending updates. If so, transfer them to the base queue.
-
- var pendingQueue = queue.shared.pending;
-
- if (pendingQueue !== null) {
- queue.shared.pending = null; // The pending queue is circular. Disconnect the pointer between first
- // and last so that it's non-circular.
-
- var lastPendingUpdate = pendingQueue;
- var firstPendingUpdate = lastPendingUpdate.next;
- lastPendingUpdate.next = null; // Append pending updates to base queue
-
- if (lastBaseUpdate === null) {
- firstBaseUpdate = firstPendingUpdate;
- } else {
- lastBaseUpdate.next = firstPendingUpdate;
- }
-
- lastBaseUpdate = lastPendingUpdate; // If there's a current queue, and it's different from the base queue, then
- // we need to transfer the updates to that queue, too. Because the base
- // queue is a singly-linked list with no cycles, we can append to both
- // lists and take advantage of structural sharing.
- // TODO: Pass `current` as argument
-
- var current = workInProgress.alternate;
-
- if (current !== null) {
- // This is always non-null on a ClassComponent or HostRoot
- var currentQueue = current.updateQueue;
- var currentLastBaseUpdate = currentQueue.lastBaseUpdate;
+ if (flags & Hydrating) {
+ finishedWork.flags &= ~Hydrating;
+ }
+}
- if (currentLastBaseUpdate !== lastBaseUpdate) {
- if (currentLastBaseUpdate === null) {
- currentQueue.firstBaseUpdate = firstPendingUpdate;
- } else {
- currentLastBaseUpdate.next = firstPendingUpdate;
- }
+function recursivelyResetForms(parentFiber) {
+ if (parentFiber.subtreeFlags & FormReset) {
+ var child = parentFiber.child;
- currentQueue.lastBaseUpdate = lastPendingUpdate;
- }
+ while (child !== null) {
+ resetFormOnFiber(child);
+ child = child.sibling;
}
- } // These values may change as we process the queue.
-
+ }
+}
- if (firstBaseUpdate !== null) {
- // Iterate through the list of updates to compute the result.
- var newState = queue.baseState; // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
- // from the original lanes.
+function resetFormOnFiber(fiber) {
+ recursivelyResetForms(fiber);
- var newLanes = NoLanes;
- var newBaseState = null;
- var newFirstBaseUpdate = null;
- var newLastBaseUpdate = null;
- var update = firstBaseUpdate;
+ if (fiber.tag === HostComponent && fiber.flags & FormReset) {
+ var formInstance = fiber.stateNode;
+ resetFormInstance(formInstance);
+ }
+}
- do {
- // An extra OffscreenLane bit is added to updates that were made to
- // a hidden tree, so that we can distinguish them from updates that were
- // already there when the tree was hidden.
- var updateLane = removeLanes(update.lane, OffscreenLane);
- var isHiddenUpdate = updateLane !== update.lane; // Check if this update was made while the tree was hidden. If so, then
- // it's not a "base" update and we should disregard the extra base lanes
- // that were added to renderLanes when we entered the Offscreen tree.
+function commitLayoutEffects(finishedWork, root, committedLanes) {
+ inProgressLanes = committedLanes;
+ inProgressRoot = root;
+ var current = finishedWork.alternate;
+ commitLayoutEffectOnFiber(root, current, finishedWork);
+ inProgressLanes = null;
+ inProgressRoot = null;
+}
- var shouldSkipUpdate = isHiddenUpdate ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane) : !isSubsetOfLanes(renderLanes, updateLane);
+function recursivelyTraverseLayoutEffects(root, parentFiber, lanes) {
+ var prevDebugFiber = getCurrentFiber();
- if (shouldSkipUpdate) {
- // Priority is insufficient. Skip this update. If this is the first
- // skipped update, the previous update/state is the new base
- // update/state.
- var clone = {
- lane: updateLane,
- tag: update.tag,
- payload: update.payload,
- callback: update.callback,
- next: null
- };
+ if (parentFiber.subtreeFlags & LayoutMask) {
+ var child = parentFiber.child;
- if (newLastBaseUpdate === null) {
- newFirstBaseUpdate = newLastBaseUpdate = clone;
- newBaseState = newState;
- } else {
- newLastBaseUpdate = newLastBaseUpdate.next = clone;
- } // Update the remaining priority in the queue.
+ while (child !== null) {
+ setCurrentFiber(child);
+ var current = child.alternate;
+ commitLayoutEffectOnFiber(root, current, child);
+ child = child.sibling;
+ }
+ }
+ setCurrentFiber(prevDebugFiber);
+}
- newLanes = mergeLanes(newLanes, updateLane);
- } else {
- // This update does have sufficient priority.
- // Check if this update is part of a pending async action. If so,
- // we'll need to suspend until the action has finished, so that it's
- // batched together with future updates in the same action.
- if (updateLane !== NoLane && updateLane === peekEntangledActionLane()) {
- didReadFromEntangledAsyncAction = true;
+function disappearLayoutEffects(finishedWork) {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case MemoComponent:
+ case SimpleMemoComponent:
+ {
+ // TODO (Offscreen) Check: flags & LayoutStatic
+ if (shouldProfile(finishedWork)) {
+ try {
+ startLayoutEffectTimer();
+ commitHookEffectListUnmount(Layout, finishedWork, finishedWork.return);
+ } finally {
+ recordLayoutEffectDuration(finishedWork);
+ }
+ } else {
+ commitHookEffectListUnmount(Layout, finishedWork, finishedWork.return);
}
- if (newLastBaseUpdate !== null) {
- var _clone = {
- // This update is going to be committed so we never want uncommit
- // it. Using NoLane works because 0 is a subset of all bitmasks, so
- // this will never be skipped by the check above.
- lane: NoLane,
- tag: update.tag,
- payload: update.payload,
- // When this update is rebased, we should not fire its
- // callback again.
- callback: null,
- next: null
- };
- newLastBaseUpdate = newLastBaseUpdate.next = _clone;
- } // Process this update.
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
+ break;
+ }
+ case ClassComponent:
+ {
+ // TODO (Offscreen) Check: flags & RefStatic
+ safelyDetachRef(finishedWork, finishedWork.return);
+ var instance = finishedWork.stateNode;
- newState = getStateFromUpdate(workInProgress, queue, update, newState, props, instance);
- var callback = update.callback;
+ if (typeof instance.componentWillUnmount === 'function') {
+ safelyCallComponentWillUnmount(finishedWork, finishedWork.return, instance);
+ }
- if (callback !== null) {
- workInProgress.flags |= Callback;
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
+ break;
+ }
- if (isHiddenUpdate) {
- workInProgress.flags |= Visibility;
- }
+ case HostHoistable:
+ case HostSingleton:
+ case HostComponent:
+ {
+ // TODO (Offscreen) Check: flags & RefStatic
+ safelyDetachRef(finishedWork, finishedWork.return);
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
+ break;
+ }
- var callbacks = queue.callbacks;
+ case OffscreenComponent:
+ {
+ // TODO (Offscreen) Check: flags & RefStatic
+ safelyDetachRef(finishedWork, finishedWork.return);
+ var isHidden = finishedWork.memoizedState !== null;
- if (callbacks === null) {
- queue.callbacks = [callback];
- } else {
- callbacks.push(callback);
- }
+ if (isHidden) ; else {
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
}
- } // $FlowFixMe[incompatible-type] we bail out when we get a null
+ break;
+ }
- update = update.next;
+ default:
+ {
+ recursivelyTraverseDisappearLayoutEffects(finishedWork);
+ break;
+ }
+ }
+}
- if (update === null) {
- pendingQueue = queue.shared.pending;
+function recursivelyTraverseDisappearLayoutEffects(parentFiber) {
+ // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
+ var child = parentFiber.child;
- if (pendingQueue === null) {
- break;
- } else {
- // An update was scheduled from inside a reducer. Add the new
- // pending updates to the end of the list and keep processing.
- var _lastPendingUpdate = pendingQueue; // Intentionally unsound. Pending updates form a circular list, but we
- // unravel them when transferring them to the base queue.
+ while (child !== null) {
+ disappearLayoutEffects(child);
+ child = child.sibling;
+ }
+}
- var _firstPendingUpdate = _lastPendingUpdate.next;
- _lastPendingUpdate.next = null;
- update = _firstPendingUpdate;
- queue.lastBaseUpdate = _lastPendingUpdate;
- queue.shared.pending = null;
- }
+function reappearLayoutEffects(finishedRoot, current, finishedWork, // This function visits both newly finished work and nodes that were re-used
+// from a previously committed tree. We cannot check non-static flags if the
+// node was reused.
+includeWorkInProgressEffects) {
+ // Turn on layout effects in a tree that previously disappeared.
+ var flags = finishedWork.flags;
+
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Check flags & LayoutStatic
+
+ commitHookLayoutEffects(finishedWork, Layout);
+ break;
}
- } while (true);
- if (newLastBaseUpdate === null) {
- newBaseState = newState;
- }
+ case ClassComponent:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Check for LayoutStatic flag
- queue.baseState = newBaseState;
- queue.firstBaseUpdate = newFirstBaseUpdate;
- queue.lastBaseUpdate = newLastBaseUpdate;
+ var instance = finishedWork.stateNode;
- if (firstBaseUpdate === null) {
- // `queue.lanes` is used for entangling transitions. We can set it back to
- // zero once the queue is empty.
- queue.shared.lanes = NoLanes;
- } // Set the remaining expiration time to be whatever is remaining in the queue.
- // This should be fine because the only two other things that contribute to
- // expiration time are props and context. We're already in the middle of the
- // begin phase by the time we start processing the queue, so we've already
- // dealt with the props. Context in components that specify
- // shouldComponentUpdate is tricky; but we'll have to account for
- // that regardless.
+ if (typeof instance.componentDidMount === 'function') {
+ try {
+ instance.componentDidMount();
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
+ } // Commit any callbacks that would have fired while the component
+ // was hidden.
- markSkippedUpdateLanes(newLanes);
- workInProgress.lanes = newLanes;
- workInProgress.memoizedState = newState;
- }
+ var updateQueue = finishedWork.updateQueue;
- {
- currentlyProcessingQueue = null;
- }
-}
+ if (updateQueue !== null) {
+ commitHiddenCallbacks(updateQueue, instance);
+ } // If this is newly finished work, check for setState callbacks
-function callCallback(callback, context) {
- if (typeof callback !== 'function') {
- throw new Error('Invalid argument passed as callback. Expected a function. Instead ' + ("received: " + callback));
- }
- callback.call(context);
-}
+ if (includeWorkInProgressEffects && flags & Callback) {
+ commitClassCallbacks(finishedWork);
+ } // TODO: Check flags & RefStatic
-function resetHasForceUpdateBeforeProcessing() {
- hasForceUpdate = false;
-}
-function checkHasForceUpdateAfterProcessing() {
- return hasForceUpdate;
-}
-function deferHiddenCallbacks(updateQueue) {
- // When an update finishes on a hidden component, its callback should not
- // be fired until/unless the component is made visible again. Stash the
- // callback on the shared queue object so it can be fired later.
- var newHiddenCallbacks = updateQueue.callbacks;
- if (newHiddenCallbacks !== null) {
- var existingHiddenCallbacks = updateQueue.shared.hiddenCallbacks;
+ safelyAttachRef(finishedWork, finishedWork.return);
+ break;
+ }
+ // Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
+ // because this function only visits nodes that are inside an
+ // Offscreen fiber.
+ // case HostRoot: {
+ // ...
+ // }
- if (existingHiddenCallbacks === null) {
- updateQueue.shared.hiddenCallbacks = newHiddenCallbacks;
- } else {
- updateQueue.shared.hiddenCallbacks = existingHiddenCallbacks.concat(newHiddenCallbacks);
- }
- }
-}
-function commitHiddenCallbacks(updateQueue, context) {
- // This component is switching from hidden -> visible. Commit any callbacks
- // that were previously deferred.
- var hiddenCallbacks = updateQueue.shared.hiddenCallbacks;
+ case HostHoistable:
+ case HostSingleton:
+ case HostComponent:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // Renderers may schedule work to be done after host components are mounted
+ // (eg DOM renderer may schedule auto-focus for inputs and form controls).
+ // These effects should only be committed when components are first mounted,
+ // aka when there is no current/alternate.
+
+ if (includeWorkInProgressEffects && current === null && flags & Update) {
+ commitHostComponentMount(finishedWork);
+ } // TODO: Check flags & Ref
- if (hiddenCallbacks !== null) {
- updateQueue.shared.hiddenCallbacks = null;
- for (var i = 0; i < hiddenCallbacks.length; i++) {
- var callback = hiddenCallbacks[i];
- callCallback(callback, context);
- }
- }
-}
-function commitCallbacks(updateQueue, context) {
- var callbacks = updateQueue.callbacks;
+ safelyAttachRef(finishedWork, finishedWork.return);
+ break;
+ }
- if (callbacks !== null) {
- updateQueue.callbacks = null;
+ case Profiler:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Figure out how Profiler updates should work with Offscreen
- for (var i = 0; i < callbacks.length; i++) {
- var callback = callbacks[i];
- callCallback(callback, context);
- }
- }
-}
+ if (includeWorkInProgressEffects && flags & Update) {
+ commitProfilerUpdate(finishedWork, current);
+ }
-var fakeInternalInstance = {};
-var didWarnAboutStateAssignmentForComponent;
-var didWarnAboutUninitializedState;
-var didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
-var didWarnAboutLegacyLifecyclesAndDerivedState;
-var didWarnAboutUndefinedDerivedState;
-var didWarnAboutDirectlyAssigningPropsToState;
-var didWarnAboutInvalidateContextType;
-var didWarnOnInvalidCallback;
+ break;
+ }
-{
- didWarnAboutStateAssignmentForComponent = new Set();
- didWarnAboutUninitializedState = new Set();
- didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
- didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
- didWarnAboutDirectlyAssigningPropsToState = new Set();
- didWarnAboutUndefinedDerivedState = new Set();
- didWarnAboutInvalidateContextType = new Set();
- didWarnOnInvalidCallback = new Set(); // This is so gross but it's at least non-critical and can be removed if
- // it causes problems. This is meant to give a nicer error message for
- // ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component,
- // ...)) which otherwise throws a "_processChildContext is not a function"
- // exception.
+ case SuspenseComponent:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Figure out how Suspense hydration callbacks should work
+ // with Offscreen.
- Object.defineProperty(fakeInternalInstance, '_processChildContext', {
- enumerable: false,
- value: function () {
- throw new Error('_processChildContext is not available in React 16+. This likely ' + 'means you have multiple copies of React and are attempting to nest ' + 'a React 15 tree inside a React 16 tree using ' + "unstable_renderSubtreeIntoContainer, which isn't supported. Try " + 'to make sure you have only one copy of React (and ideally, switch ' + 'to ReactDOM.createPortal).');
- }
- });
- Object.freeze(fakeInternalInstance);
-}
+ if (includeWorkInProgressEffects && flags & Update) {
+ commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
+ }
-function warnOnInvalidCallback(callback) {
- {
- if (callback === null || typeof callback === 'function') {
- return;
- } // eslint-disable-next-line react-internal/safe-string-coercion
+ break;
+ }
+
+ case OffscreenComponent:
+ {
+ var offscreenState = finishedWork.memoizedState;
+ var isHidden = offscreenState !== null;
+ if (isHidden) ; else {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
+ } // TODO: Check flags & Ref
- var key = String(callback);
- if (!didWarnOnInvalidCallback.has(key)) {
- didWarnOnInvalidCallback.add(key);
+ safelyAttachRef(finishedWork, finishedWork.return);
+ break;
+ }
- error('Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback);
- }
+ default:
+ {
+ recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
+ break;
+ }
}
}
-function warnOnUndefinedDerivedState(type, partialState) {
- {
- if (partialState === undefined) {
- var componentName = getComponentNameFromType(type) || 'Component';
+function recursivelyTraverseReappearLayoutEffects(finishedRoot, parentFiber, includeWorkInProgressEffects) {
+ // This function visits both newly finished work and nodes that were re-used
+ // from a previously committed tree. We cannot check non-static flags if the
+ // node was reused.
+ var childShouldIncludeWorkInProgressEffects = includeWorkInProgressEffects && (parentFiber.subtreeFlags & LayoutMask) !== NoFlags$1; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
- didWarnAboutUndefinedDerivedState.add(componentName);
+ var prevDebugFiber = getCurrentFiber();
+ var child = parentFiber.child;
- error('%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' + 'You have returned undefined.', componentName);
- }
- }
+ while (child !== null) {
+ var current = child.alternate;
+ reappearLayoutEffects(finishedRoot, current, child, childShouldIncludeWorkInProgressEffects);
+ child = child.sibling;
}
+
+ setCurrentFiber(prevDebugFiber);
}
-function applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, nextProps) {
- var prevState = workInProgress.memoizedState;
- var partialState = getDerivedStateFromProps(nextProps, prevState);
+function commitHookPassiveMountEffects(finishedWork, hookFlags) {
+ if (shouldProfile(finishedWork)) {
+ startPassiveEffectTimer();
- {
- if (workInProgress.mode & StrictLegacyMode) {
- setIsStrictModeForDevtools(true);
+ try {
+ commitHookEffectListMount(hookFlags, finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ }
- try {
- // Invoke the function an extra time to help detect side-effects.
- partialState = getDerivedStateFromProps(nextProps, prevState);
- } finally {
- setIsStrictModeForDevtools(false);
- }
+ recordPassiveEffectDuration(finishedWork);
+ } else {
+ try {
+ commitHookEffectListMount(hookFlags, finishedWork);
+ } catch (error) {
+ captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
+ }
+}
- warnOnUndefinedDerivedState(ctor, partialState);
- } // Merge the partial state and the previous state.
+function commitOffscreenPassiveMountEffects(current, finishedWork, instance) {
+ {
+ var previousCache = null;
+ if (current !== null && current.memoizedState !== null && current.memoizedState.cachePool !== null) {
+ previousCache = current.memoizedState.cachePool.pool;
+ }
- var memoizedState = partialState === null || partialState === undefined ? prevState : assign({}, prevState, partialState);
- workInProgress.memoizedState = memoizedState; // Once the update queue is empty, persist the derived state onto the
- // base state.
+ var nextCache = null;
- if (workInProgress.lanes === NoLanes) {
- // Queue is always non-null for classes
- var updateQueue = workInProgress.updateQueue;
- updateQueue.baseState = memoizedState;
- }
-}
+ if (finishedWork.memoizedState !== null && finishedWork.memoizedState.cachePool !== null) {
+ nextCache = finishedWork.memoizedState.cachePool.pool;
+ } // Retain/release the cache used for pending (suspended) nodes.
+ // Note that this is only reached in the non-suspended/visible case:
+ // when the content is suspended/hidden, the retain/release occurs
+ // via the parent Suspense component (see case above).
-var classComponentUpdater = {
- isMounted: isMounted,
- // $FlowFixMe[missing-local-annot]
- enqueueSetState: function (inst, payload, callback) {
- var fiber = get(inst);
- var lane = requestUpdateLane(fiber);
- var update = createUpdate(lane);
- update.payload = payload;
- if (callback !== undefined && callback !== null) {
- {
- warnOnInvalidCallback(callback);
+ if (nextCache !== previousCache) {
+ if (nextCache != null) {
+ retainCache(nextCache);
}
- update.callback = callback;
+ if (previousCache != null) {
+ releaseCache(previousCache);
+ }
}
+ }
+}
- var root = enqueueUpdate(fiber, update, lane);
+function commitCachePassiveMountEffect(current, finishedWork) {
+ {
+ var previousCache = null;
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane);
- entangleTransitions(root, fiber, lane);
+ if (finishedWork.alternate !== null) {
+ previousCache = finishedWork.alternate.memoizedState.cache;
}
- {
- markStateUpdateScheduled(fiber, lane);
- }
- },
- enqueueReplaceState: function (inst, payload, callback) {
- var fiber = get(inst);
- var lane = requestUpdateLane(fiber);
- var update = createUpdate(lane);
- update.tag = ReplaceState;
- update.payload = payload;
+ var nextCache = finishedWork.memoizedState.cache; // Retain/release the cache. In theory the cache component
+ // could be "borrowing" a cache instance owned by some parent,
+ // in which case we could avoid retaining/releasing. But it
+ // is non-trivial to determine when that is the case, so we
+ // always retain/release.
- if (callback !== undefined && callback !== null) {
- {
- warnOnInvalidCallback(callback);
- }
+ if (nextCache !== previousCache) {
+ retainCache(nextCache);
- update.callback = callback;
+ if (previousCache != null) {
+ releaseCache(previousCache);
+ }
}
+ }
+}
- var root = enqueueUpdate(fiber, update, lane);
+function commitPassiveMountEffects(root, finishedWork, committedLanes, committedTransitions) {
+ setCurrentFiber(finishedWork);
+ commitPassiveMountOnFiber(root, finishedWork, committedLanes, committedTransitions);
+ resetCurrentFiber();
+}
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane);
- entangleTransitions(root, fiber, lane);
- }
+function recursivelyTraversePassiveMountEffects(root, parentFiber, committedLanes, committedTransitions) {
+ var prevDebugFiber = getCurrentFiber();
- {
- markStateUpdateScheduled(fiber, lane);
+ if (parentFiber.subtreeFlags & PassiveMask) {
+ var child = parentFiber.child;
+
+ while (child !== null) {
+ setCurrentFiber(child);
+ commitPassiveMountOnFiber(root, child, committedLanes, committedTransitions);
+ child = child.sibling;
}
- },
- // $FlowFixMe[missing-local-annot]
- enqueueForceUpdate: function (inst, callback) {
- var fiber = get(inst);
- var lane = requestUpdateLane(fiber);
- var update = createUpdate(lane);
- update.tag = ForceUpdate;
+ }
- if (callback !== undefined && callback !== null) {
+ setCurrentFiber(prevDebugFiber);
+}
+
+function commitPassiveMountOnFiber(finishedRoot, finishedWork, committedLanes, committedTransitions) {
+ // When updating this function, also update reconnectPassiveEffects, which does
+ // most of the same things when an offscreen tree goes from hidden -> visible,
+ // or when toggling effects inside a hidden tree.
+ var flags = finishedWork.flags;
+
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
{
- warnOnInvalidCallback(callback);
- }
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- update.callback = callback;
- }
+ if (flags & Passive$1) {
+ commitHookPassiveMountEffects(finishedWork, Passive | HasEffect);
+ }
- var root = enqueueUpdate(fiber, update, lane);
+ break;
+ }
- if (root !== null) {
- scheduleUpdateOnFiber(root, fiber, lane);
- entangleTransitions(root, fiber, lane);
- }
+ case HostRoot:
+ {
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- {
- markForceUpdateScheduled(fiber, lane);
- }
- }
-};
+ if (flags & Passive$1) {
+ {
+ var previousCache = null;
-function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
- var instance = workInProgress.stateNode;
+ if (finishedWork.alternate !== null) {
+ previousCache = finishedWork.alternate.memoizedState.cache;
+ }
- if (typeof instance.shouldComponentUpdate === 'function') {
- var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
+ var nextCache = finishedWork.memoizedState.cache; // Retain/release the root cache.
+ // Note that on initial mount, previousCache and nextCache will be the same
+ // and this retain won't occur. To counter this, we instead retain the HostRoot's
+ // initial cache when creating the root itself (see createFiberRoot() in
+ // ReactFiberRoot.js). Subsequent updates that change the cache are reflected
+ // here, such that previous/next caches are retained correctly.
- {
- if (workInProgress.mode & StrictLegacyMode) {
- setIsStrictModeForDevtools(true);
+ if (nextCache !== previousCache) {
+ retainCache(nextCache);
- try {
- // Invoke the function an extra time to help detect side-effects.
- shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext);
- } finally {
- setIsStrictModeForDevtools(false);
+ if (previousCache != null) {
+ releaseCache(previousCache);
+ }
+ }
+ }
}
- }
- if (shouldUpdate === undefined) {
- error('%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentNameFromType(ctor) || 'Component');
+ break;
}
- }
- return shouldUpdate;
- }
+ case LegacyHiddenComponent:
+ {
- if (ctor.prototype && ctor.prototype.isPureReactComponent) {
- return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState);
- }
+ break;
+ }
- return true;
-}
+ case OffscreenComponent:
+ {
+ // TODO: Pass `current` as argument to this function
+ var _instance3 = finishedWork.stateNode;
+ var nextState = finishedWork.memoizedState;
+ var isHidden = nextState !== null;
-function checkClassInstance(workInProgress, ctor, newProps) {
- var instance = workInProgress.stateNode;
+ if (isHidden) {
+ if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
+ // The effects are currently connected. Update them.
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
+ } else {
+ {
+ // The effects are currently disconnected. Since the tree is hidden,
+ // don't connect them. This also applies to the initial render.
+ {
+ // "Atomic" effects are ones that need to fire on every commit,
+ // even during pre-rendering. An example is updating the reference
+ // count on cache instances.
+ recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
+ }
+ }
+ }
+ } else {
+ // Tree is visible
+ if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
+ // The effects are currently connected. Update them.
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
+ } else {
+ // The effects are currently disconnected. Reconnect them, while also
+ // firing effects inside newly mounted trees. This also applies to
+ // the initial render.
+ _instance3._visibility |= OffscreenPassiveEffectsConnected;
+ var includeWorkInProgressEffects = (finishedWork.subtreeFlags & PassiveMask) !== NoFlags$1;
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
+ }
+ }
- {
- var name = getComponentNameFromType(ctor) || 'Component';
- var renderPresent = instance.render;
+ if (flags & Passive$1) {
+ var _current = finishedWork.alternate;
+ commitOffscreenPassiveMountEffects(_current, finishedWork);
+ }
- if (!renderPresent) {
- if (ctor.prototype && typeof ctor.prototype.render === 'function') {
- error('No `render` method found on the %s ' + 'instance: did you accidentally return an object from the constructor?', name);
- } else {
- error('No `render` method found on the %s ' + 'instance: you may have forgotten to define `render`.', name);
+ break;
}
- }
- if (instance.getInitialState && !instance.getInitialState.isReactClassApproved && !instance.state) {
- error('getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', name);
- }
+ case CacheComponent:
+ {
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- if (instance.getDefaultProps && !instance.getDefaultProps.isReactClassApproved) {
- error('getDefaultProps was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.', name);
- }
+ if (flags & Passive$1) {
+ // TODO: Pass `current` as argument to this function
+ var _current2 = finishedWork.alternate;
+ commitCachePassiveMountEffect(_current2, finishedWork);
+ }
- if (instance.propTypes) {
- error('propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', name);
- }
+ break;
+ }
- if (instance.contextType) {
- error('contextType was defined as an instance property on %s. Use a static ' + 'property to define contextType instead.', name);
- }
+ case TracingMarkerComponent:
- {
- if (ctor.childContextTypes) {
- error('%s uses the legacy childContextTypes API which was removed in React 19. ' + 'Use React.createContext() instead.', name);
+ default:
+ {
+ recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
+ break;
}
+ }
+}
- if (ctor.contextTypes) {
- error('%s uses the legacy contextTypes API which was removed in React 19. ' + 'Use React.createContext() with static contextType instead.', name);
- }
- }
+function recursivelyTraverseReconnectPassiveEffects(finishedRoot, parentFiber, committedLanes, committedTransitions, includeWorkInProgressEffects) {
+ // This function visits both newly finished work and nodes that were re-used
+ // from a previously committed tree. We cannot check non-static flags if the
+ // node was reused.
+ var childShouldIncludeWorkInProgressEffects = includeWorkInProgressEffects && (parentFiber.subtreeFlags & PassiveMask) !== NoFlags$1; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- if (typeof instance.componentShouldUpdate === 'function') {
- error('%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', name);
- }
+ var prevDebugFiber = getCurrentFiber();
+ var child = parentFiber.child;
- if (ctor.prototype && ctor.prototype.isPureReactComponent && typeof instance.shouldComponentUpdate !== 'undefined') {
- error('%s has a method called shouldComponentUpdate(). ' + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' + 'Please extend React.Component if shouldComponentUpdate is used.', getComponentNameFromType(ctor) || 'A pure component');
- }
+ while (child !== null) {
+ reconnectPassiveEffects(finishedRoot, child, committedLanes, committedTransitions, childShouldIncludeWorkInProgressEffects);
+ child = child.sibling;
+ }
- if (typeof instance.componentDidUnmount === 'function') {
- error('%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', name);
- }
+ setCurrentFiber(prevDebugFiber);
+}
- if (typeof instance.componentDidReceiveProps === 'function') {
- error('%s has a method called ' + 'componentDidReceiveProps(). But there is no such lifecycle method. ' + 'If you meant to update the state in response to changing props, ' + 'use componentWillReceiveProps(). If you meant to fetch data or ' + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', name);
- }
+function reconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, // This function visits both newly finished work and nodes that were re-used
+// from a previously committed tree. We cannot check non-static flags if the
+// node was reused.
+includeWorkInProgressEffects) {
+ var flags = finishedWork.flags;
- if (typeof instance.componentWillRecieveProps === 'function') {
- error('%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', name);
- }
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects); // TODO: Check for PassiveStatic flag
- if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') {
- error('%s has a method called ' + 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?', name);
- }
+ commitHookPassiveMountEffects(finishedWork, Passive);
+ break;
+ }
+ // Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
+ // because this function only visits nodes that are inside an
+ // Offscreen fiber.
+ // case HostRoot: {
+ // ...
+ // }
- var hasMutatedProps = instance.props !== newProps;
+ case LegacyHiddenComponent:
+ {
+
+ break;
+ }
+
+ case OffscreenComponent:
+ {
+ var _instance4 = finishedWork.stateNode;
+ var nextState = finishedWork.memoizedState;
+ var isHidden = nextState !== null;
+
+ if (isHidden) {
+ if (_instance4._visibility & OffscreenPassiveEffectsConnected) {
+ // The effects are currently connected. Update them.
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
+ } else {
+ {
+ // The effects are currently disconnected. Since the tree is hidden,
+ // don't connect them. This also applies to the initial render.
+ {
+ // "Atomic" effects are ones that need to fire on every commit,
+ // even during pre-rendering. An example is updating the reference
+ // count on cache instances.
+ recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
+ }
+ }
+ }
+ } else {
+ // Tree is visible
+ // Since we're already inside a reconnecting tree, it doesn't matter
+ // whether the effects are currently connected. In either case, we'll
+ // continue traversing the tree and firing all the effects.
+ //
+ // We do need to set the "connected" flag on the instance, though.
+ _instance4._visibility |= OffscreenPassiveEffectsConnected;
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
+ }
- if (instance.props !== undefined && hasMutatedProps) {
- error('When calling super() in `%s`, make sure to pass ' + "up the same props that your component's constructor was passed.", name);
- }
+ if (includeWorkInProgressEffects && flags & Passive$1) {
+ // TODO: Pass `current` as argument to this function
+ var _current3 = finishedWork.alternate;
+ commitOffscreenPassiveMountEffects(_current3, finishedWork);
+ }
- if (instance.defaultProps) {
- error('Setting defaultProps as an instance property on %s is not supported and will be ignored.' + ' Instead, define defaultProps as a static property on %s.', name, name);
- }
+ break;
+ }
- if (typeof instance.getSnapshotBeforeUpdate === 'function' && typeof instance.componentDidUpdate !== 'function' && !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)) {
- didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor);
+ case CacheComponent:
+ {
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
- error('%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' + 'This component defines getSnapshotBeforeUpdate() only.', getComponentNameFromType(ctor));
- }
+ if (includeWorkInProgressEffects && flags & Passive$1) {
+ // TODO: Pass `current` as argument to this function
+ var _current4 = finishedWork.alternate;
+ commitCachePassiveMountEffect(_current4, finishedWork);
+ }
- if (typeof instance.getDerivedStateFromProps === 'function') {
- error('%s: getDerivedStateFromProps() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.', name);
- }
+ break;
+ }
- if (typeof instance.getDerivedStateFromError === 'function') {
- error('%s: getDerivedStateFromError() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.', name);
- }
+ case TracingMarkerComponent:
- if (typeof ctor.getSnapshotBeforeUpdate === 'function') {
- error('%s: getSnapshotBeforeUpdate() is defined as a static method ' + 'and will be ignored. Instead, declare it as an instance method.', name);
- }
+ default:
+ {
+ recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
+ break;
+ }
+ }
+}
- var state = instance.state;
+function recursivelyTraverseAtomicPassiveEffects(finishedRoot, parentFiber, committedLanes, committedTransitions) {
+ // "Atomic" effects are ones that need to fire on every commit, even during
+ // pre-rendering. We call this function when traversing a hidden tree whose
+ // regular effects are currently disconnected.
+ var prevDebugFiber = getCurrentFiber(); // TODO: Add special flag for atomic effects
- if (state && (typeof state !== 'object' || isArray(state))) {
- error('%s.state: must be set to an object or null', name);
- }
+ if (parentFiber.subtreeFlags & PassiveMask) {
+ var child = parentFiber.child;
- if (typeof instance.getChildContext === 'function' && typeof ctor.childContextTypes !== 'object') {
- error('%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', name);
+ while (child !== null) {
+ setCurrentFiber(child);
+ commitAtomicPassiveEffects(finishedRoot, child);
+ child = child.sibling;
}
}
-}
-function constructClassInstance(workInProgress, ctor, props) {
- var context = emptyContextObject;
- var contextType = ctor.contextType;
+ setCurrentFiber(prevDebugFiber);
+}
- {
- if ('contextType' in ctor) {
- var isValid = // Allow null for conditional declaration
- contextType === null || contextType !== undefined && contextType.$$typeof === REACT_CONTEXT_TYPE;
+function commitAtomicPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions) {
+ // "Atomic" effects are ones that need to fire on every commit, even during
+ // pre-rendering. We call this function when traversing a hidden tree whose
+ // regular effects are currently disconnected.
+ var flags = finishedWork.flags;
- if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) {
- didWarnAboutInvalidateContextType.add(ctor);
- var addendum = '';
+ switch (finishedWork.tag) {
+ case OffscreenComponent:
+ {
+ recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
- if (contextType === undefined) {
- addendum = ' However, it is set to undefined. ' + 'This can be caused by a typo or by mixing up named and default imports. ' + 'This can also happen due to a circular dependency, so ' + 'try moving the createContext() call to a separate file.';
- } else if (typeof contextType !== 'object') {
- addendum = ' However, it is set to a ' + typeof contextType + '.';
- } else if (contextType.$$typeof === REACT_CONSUMER_TYPE) {
- addendum = ' Did you accidentally pass the Context.Consumer instead?';
- } else {
- addendum = ' However, it is set to an object with keys {' + Object.keys(contextType).join(', ') + '}.';
+ if (flags & Passive$1) {
+ // TODO: Pass `current` as argument to this function
+ var current = finishedWork.alternate;
+ commitOffscreenPassiveMountEffects(current, finishedWork);
}
- error('%s defines an invalid contextType. ' + 'contextType should point to the Context object returned by React.createContext().%s', getComponentNameFromType(ctor) || 'Component', addendum);
+ break;
}
- }
- }
- if (typeof contextType === 'object' && contextType !== null) {
- context = readContext(contextType);
- }
+ case CacheComponent:
+ {
+ recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
- var instance = new ctor(props, context); // Instantiate twice to help detect side-effects.
+ if (flags & Passive$1) {
+ // TODO: Pass `current` as argument to this function
+ var _current5 = finishedWork.alternate;
+ commitCachePassiveMountEffect(_current5, finishedWork);
+ }
- {
- if (workInProgress.mode & StrictLegacyMode) {
- setIsStrictModeForDevtools(true);
+ break;
+ }
- try {
- instance = new ctor(props, context); // eslint-disable-line no-new
- } finally {
- setIsStrictModeForDevtools(false);
+ default:
+ {
+ recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
+ break;
}
- }
}
+}
- var state = workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null;
- instance.updater = classComponentUpdater;
- workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates
+function commitPassiveUnmountEffects(finishedWork) {
+ setCurrentFiber(finishedWork);
+ commitPassiveUnmountOnFiber(finishedWork);
+ resetCurrentFiber();
+} // If we're inside a brand new tree, or a tree that was already visible, then we
+// should only suspend host components that have a ShouldSuspendCommit flag.
+// Components without it haven't changed since the last commit, so we can skip
+// over those.
+//
+// When we enter a tree that is being revealed (going from hidden -> visible),
+// we need to suspend _any_ component that _may_ suspend. Even if they're
+// already in the "current" tree. Because their visibility has changed, the
+// browser may not have prerendered them yet. So we check the MaySuspendCommit
+// flag instead.
- set(instance, workInProgress);
+var suspenseyCommitFlag = ShouldSuspendCommit;
+function accumulateSuspenseyCommit(finishedWork) {
+ accumulateSuspenseyCommitOnFiber(finishedWork);
+}
- {
- instance._reactInternalInstance = fakeInternalInstance;
+function recursivelyAccumulateSuspenseyCommit(parentFiber) {
+ if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
+ var child = parentFiber.child;
+
+ while (child !== null) {
+ accumulateSuspenseyCommitOnFiber(child);
+ child = child.sibling;
+ }
}
+}
- {
- if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) {
- var componentName = getComponentNameFromType(ctor) || 'Component';
+function accumulateSuspenseyCommitOnFiber(fiber) {
+ switch (fiber.tag) {
+ case HostHoistable:
+ {
+ recursivelyAccumulateSuspenseyCommit(fiber);
- if (!didWarnAboutUninitializedState.has(componentName)) {
- didWarnAboutUninitializedState.add(componentName);
+ if (fiber.flags & suspenseyCommitFlag) {
+ if (fiber.memoizedState !== null) {
+ suspendResource( // This should always be set by visiting HostRoot first
+ currentHoistableRoot, fiber.memoizedState, fiber.memoizedProps);
+ }
+ }
- error('`%s` uses `getDerivedStateFromProps` but its initial state is ' + '%s. This is not recommended. Instead, define the initial state by ' + 'assigning an object to `this.state` in the constructor of `%s`. ' + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.', componentName, instance.state === null ? 'null' : 'undefined', componentName);
+ break;
}
- } // If new component APIs are defined, "unsafe" lifecycles won't be called.
- // Warn about these lifecycles if they are present.
- // Don't warn about react-lifecycles-compat polyfilled methods though.
+ case HostComponent:
+ {
+ recursivelyAccumulateSuspenseyCommit(fiber);
- if (typeof ctor.getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function') {
- var foundWillMountName = null;
- var foundWillReceivePropsName = null;
- var foundWillUpdateName = null;
+ break;
+ }
- if (typeof instance.componentWillMount === 'function' && instance.componentWillMount.__suppressDeprecationWarning !== true) {
- foundWillMountName = 'componentWillMount';
- } else if (typeof instance.UNSAFE_componentWillMount === 'function') {
- foundWillMountName = 'UNSAFE_componentWillMount';
+ case HostRoot:
+ case HostPortal:
+ {
+ {
+ var previousHoistableRoot = currentHoistableRoot;
+ var container = fiber.stateNode.containerInfo;
+ currentHoistableRoot = getHoistableRoot(container);
+ recursivelyAccumulateSuspenseyCommit(fiber);
+ currentHoistableRoot = previousHoistableRoot;
+ }
+
+ break;
}
- if (typeof instance.componentWillReceiveProps === 'function' && instance.componentWillReceiveProps.__suppressDeprecationWarning !== true) {
- foundWillReceivePropsName = 'componentWillReceiveProps';
- } else if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
- foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
+ case OffscreenComponent:
+ {
+ var isHidden = fiber.memoizedState !== null;
+
+ if (isHidden) ; else {
+ var current = fiber.alternate;
+ var wasHidden = current !== null && current.memoizedState !== null;
+
+ if (wasHidden) {
+ // This tree is being revealed. Visit all newly visible suspensey
+ // instances, even if they're in the current tree.
+ var prevFlags = suspenseyCommitFlag;
+ suspenseyCommitFlag = MaySuspendCommit;
+ recursivelyAccumulateSuspenseyCommit(fiber);
+ suspenseyCommitFlag = prevFlags;
+ } else {
+ recursivelyAccumulateSuspenseyCommit(fiber);
+ }
+ }
+
+ break;
}
- if (typeof instance.componentWillUpdate === 'function' && instance.componentWillUpdate.__suppressDeprecationWarning !== true) {
- foundWillUpdateName = 'componentWillUpdate';
- } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
- foundWillUpdateName = 'UNSAFE_componentWillUpdate';
+ default:
+ {
+ recursivelyAccumulateSuspenseyCommit(fiber);
}
+ }
+}
+
+function detachAlternateSiblings(parentFiber) {
+ // A fiber was deleted from this parent fiber, but it's still part of the
+ // previous (alternate) parent fiber's list of children. Because children
+ // are a linked list, an earlier sibling that's still alive will be
+ // connected to the deleted fiber via its `alternate`:
+ //
+ // live fiber --alternate--> previous live fiber --sibling--> deleted
+ // fiber
+ //
+ // We can't disconnect `alternate` on nodes that haven't been deleted yet,
+ // but we can disconnect the `sibling` and `child` pointers.
+ var previousFiber = parentFiber.alternate;
- if (foundWillMountName !== null || foundWillReceivePropsName !== null || foundWillUpdateName !== null) {
- var _componentName = getComponentNameFromType(ctor) || 'Component';
+ if (previousFiber !== null) {
+ var detachedChild = previousFiber.child;
- var newApiName = typeof ctor.getDerivedStateFromProps === 'function' ? 'getDerivedStateFromProps()' : 'getSnapshotBeforeUpdate()';
+ if (detachedChild !== null) {
+ previousFiber.child = null;
- if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(_componentName)) {
- didWarnAboutLegacyLifecyclesAndDerivedState.add(_componentName);
+ do {
+ // $FlowFixMe[incompatible-use] found when upgrading Flow
+ var detachedSibling = detachedChild.sibling; // $FlowFixMe[incompatible-use] found when upgrading Flow
- error('Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' + 'The above lifecycles should be removed. Learn more about this warning here:\n' + 'https://react.dev/link/unsafe-component-lifecycles', _componentName, newApiName, foundWillMountName !== null ? "\n " + foundWillMountName : '', foundWillReceivePropsName !== null ? "\n " + foundWillReceivePropsName : '', foundWillUpdateName !== null ? "\n " + foundWillUpdateName : '');
- }
- }
+ detachedChild.sibling = null;
+ detachedChild = detachedSibling;
+ } while (detachedChild !== null);
}
- } // Cache unmasked context so we can avoid recreating masked context unless necessary.
-
- return instance;
+ }
}
-function callComponentWillMount(workInProgress, instance) {
- var oldState = instance.state;
-
- if (typeof instance.componentWillMount === 'function') {
- instance.componentWillMount();
+function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, hookFlags) {
+ if (shouldProfile(finishedWork)) {
+ startPassiveEffectTimer();
+ commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
+ recordPassiveEffectDuration(finishedWork);
+ } else {
+ commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
}
+}
- if (typeof instance.UNSAFE_componentWillMount === 'function') {
- instance.UNSAFE_componentWillMount();
- }
+function recursivelyTraversePassiveUnmountEffects(parentFiber) {
+ // Deletions effects can be scheduled on any fiber type. They need to happen
+ // before the children effects have fired.
+ var deletions = parentFiber.deletions;
- if (oldState !== instance.state) {
- {
- error('%s.componentWillMount(): Assigning directly to this.state is ' + "deprecated (except inside a component's " + 'constructor). Use setState instead.', getComponentNameFromFiber(workInProgress) || 'Component');
+ if ((parentFiber.flags & ChildDeletion) !== NoFlags$1) {
+ if (deletions !== null) {
+ for (var i = 0; i < deletions.length; i++) {
+ var childToDelete = deletions[i]; // TODO: Convert this to use recursion
+
+ nextEffect = childToDelete;
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin(childToDelete, parentFiber);
+ }
}
- classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
+ detachAlternateSiblings(parentFiber);
}
-}
-function callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext) {
- var oldState = instance.state;
+ var prevDebugFiber = getCurrentFiber(); // TODO: Split PassiveMask into separate masks for mount and unmount?
- if (typeof instance.componentWillReceiveProps === 'function') {
- instance.componentWillReceiveProps(newProps, nextContext);
- }
+ if (parentFiber.subtreeFlags & PassiveMask) {
+ var child = parentFiber.child;
- if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') {
- instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
+ while (child !== null) {
+ setCurrentFiber(child);
+ commitPassiveUnmountOnFiber(child);
+ child = child.sibling;
+ }
}
- if (instance.state !== oldState) {
- {
- var componentName = getComponentNameFromFiber(workInProgress) || 'Component';
+ setCurrentFiber(prevDebugFiber);
+}
- if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
- didWarnAboutStateAssignmentForComponent.add(componentName);
+function commitPassiveUnmountOnFiber(finishedWork) {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
- error('%s.componentWillReceiveProps(): Assigning directly to ' + "this.state is deprecated (except inside a component's " + 'constructor). Use setState instead.', componentName);
- }
- }
+ if (finishedWork.flags & Passive$1) {
+ commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, Passive | HasEffect);
+ }
- classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
- }
-} // Invokes the mount life-cycles on a previously never rendered instance.
+ break;
+ }
+ case OffscreenComponent:
+ {
+ var instance = finishedWork.stateNode;
+ var nextState = finishedWork.memoizedState;
+ var isHidden = nextState !== null;
-function mountClassInstance(workInProgress, ctor, newProps, renderLanes) {
- {
- checkClassInstance(workInProgress, ctor, newProps);
- }
+ if (isHidden && instance._visibility & OffscreenPassiveEffectsConnected && ( // For backwards compatibility, don't unmount when a tree suspends. In
+ // the future we may change this to unmount after a delay.
+ finishedWork.return === null || finishedWork.return.tag !== SuspenseComponent)) {
+ // The effects are currently connected. Disconnect them.
+ // TODO: Add option or heuristic to delay before disconnecting the
+ // effects. Then if the tree reappears before the delay has elapsed, we
+ // can skip toggling the effects entirely.
+ instance._visibility &= ~OffscreenPassiveEffectsConnected;
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
+ } else {
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
+ }
- var instance = workInProgress.stateNode;
- instance.props = newProps;
- instance.state = workInProgress.memoizedState;
- instance.refs = {};
- initializeUpdateQueue(workInProgress);
- var contextType = ctor.contextType;
+ break;
+ }
- if (typeof contextType === 'object' && contextType !== null) {
- instance.context = readContext(contextType);
- } else {
- instance.context = emptyContextObject;
+ default:
+ {
+ recursivelyTraversePassiveUnmountEffects(finishedWork);
+ break;
+ }
}
+}
- {
- if (instance.state === newProps) {
- var componentName = getComponentNameFromType(ctor) || 'Component';
+function recursivelyTraverseDisconnectPassiveEffects(parentFiber) {
+ // Deletions effects can be scheduled on any fiber type. They need to happen
+ // before the children effects have fired.
+ var deletions = parentFiber.deletions;
- if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) {
- didWarnAboutDirectlyAssigningPropsToState.add(componentName);
+ if ((parentFiber.flags & ChildDeletion) !== NoFlags$1) {
+ if (deletions !== null) {
+ for (var i = 0; i < deletions.length; i++) {
+ var childToDelete = deletions[i]; // TODO: Convert this to use recursion
- error('%s: It is not recommended to assign props directly to state ' + "because updates to props won't be reflected in state. " + 'In most cases, it is better to use props directly.', componentName);
+ nextEffect = childToDelete;
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin(childToDelete, parentFiber);
}
}
- if (workInProgress.mode & StrictLegacyMode) {
- ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, instance);
- }
-
- ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(workInProgress, instance);
+ detachAlternateSiblings(parentFiber);
}
- instance.state = workInProgress.memoizedState;
- var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
+ var prevDebugFiber = getCurrentFiber(); // TODO: Check PassiveStatic flag
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
- instance.state = workInProgress.memoizedState;
- } // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
+ var child = parentFiber.child;
+ while (child !== null) {
+ setCurrentFiber(child);
+ disconnectPassiveEffect(child);
+ child = child.sibling;
+ }
- if (typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')) {
- callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's
- // process them now.
+ setCurrentFiber(prevDebugFiber);
+}
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- suspendIfUpdateReadFromEntangledAsyncAction();
- instance.state = workInProgress.memoizedState;
- }
+function disconnectPassiveEffect(finishedWork) {
+ switch (finishedWork.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ // TODO: Check PassiveStatic flag
+ commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, Passive); // When disconnecting passive effects, we fire the effects in the same
+ // order as during a deletiong: parent before child
- if (typeof instance.componentDidMount === 'function') {
- workInProgress.flags |= Update | LayoutStatic;
- }
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
+ break;
+ }
- if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
- workInProgress.flags |= MountLayoutDev;
- }
-}
+ case OffscreenComponent:
+ {
+ var instance = finishedWork.stateNode;
-function resumeMountClassInstance(workInProgress, ctor, newProps, renderLanes) {
- var instance = workInProgress.stateNode;
- var unresolvedOldProps = workInProgress.memoizedProps;
- var oldProps = resolveClassComponentProps(ctor, unresolvedOldProps);
- instance.props = oldProps;
- var oldContext = instance.context;
- var contextType = ctor.contextType;
- var nextContext = emptyContextObject;
+ if (instance._visibility & OffscreenPassiveEffectsConnected) {
+ instance._visibility &= ~OffscreenPassiveEffectsConnected;
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
+ }
- if (typeof contextType === 'object' && contextType !== null) {
- nextContext = readContext(contextType);
+ break;
+ }
+
+ default:
+ {
+ recursivelyTraverseDisconnectPassiveEffects(finishedWork);
+ break;
+ }
}
+}
- var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
- var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // When comparing whether props changed, we should compare using the
- // unresolved props object that is stored on the fiber, rather than the
- // one that gets assigned to the instance, because that object may have been
- // cloned to resolve default props and/or remove `ref`.
+function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(deletedSubtreeRoot, nearestMountedAncestor) {
+ while (nextEffect !== null) {
+ var fiber = nextEffect; // Deletion effects fire in parent -> child order
+ // TODO: Check if fiber has a PassiveStatic flag
- var unresolvedNewProps = workInProgress.pendingProps;
- var didReceiveNewProps = unresolvedNewProps !== unresolvedOldProps; // Note: During these life-cycles, instance.props/instance.state are what
- // ever the previously attempted to render - not the "current". However,
- // during componentDidUpdate we pass the "current" props.
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
+ setCurrentFiber(fiber);
+ commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
+ resetCurrentFiber();
+ var child = fiber.child; // TODO: Only traverse subtree if it has a PassiveStatic flag.
- if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) {
- if (didReceiveNewProps || oldContext !== nextContext) {
- callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext);
+ if (child !== null) {
+ child.return = fiber;
+ nextEffect = child;
+ } else {
+ commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot);
}
}
+}
+
+function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot) {
+ while (nextEffect !== null) {
+ var fiber = nextEffect;
+ var sibling = fiber.sibling;
+ var returnFiber = fiber.return; // Recursively traverse the entire deleted tree and clean up fiber fields.
+ // This is more aggressive than ideal, and the long term goal is to only
+ // have to detach the deleted tree at the root.
- resetHasForceUpdateBeforeProcessing();
- var oldState = workInProgress.memoizedState;
- var newState = instance.state = oldState;
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- suspendIfUpdateReadFromEntangledAsyncAction();
- newState = workInProgress.memoizedState;
+ detachFiberAfterEffects(fiber);
- if (!didReceiveNewProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidMount === 'function') {
- workInProgress.flags |= Update | LayoutStatic;
+ if (fiber === deletedSubtreeRoot) {
+ nextEffect = null;
+ return;
}
- if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
- workInProgress.flags |= MountLayoutDev;
+ if (sibling !== null) {
+ sibling.return = returnFiber;
+ nextEffect = sibling;
+ return;
}
- return false;
+ nextEffect = returnFiber;
}
+}
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
- newState = workInProgress.memoizedState;
- }
+function commitPassiveUnmountInsideDeletedTreeOnFiber(current, nearestMountedAncestor) {
+ switch (current.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ commitHookPassiveUnmountEffects(current, nearestMountedAncestor, Passive);
+ break;
+ }
+ // TODO: run passive unmount effects when unmounting a root.
+ // Because passive unmount effects are not currently run,
+ // the cache instance owned by the root will never be freed.
+ // When effects are run, the cache should be freed here:
+ // case HostRoot: {
+ // if (enableCache) {
+ // const cache = current.memoizedState.cache;
+ // releaseCache(cache);
+ // }
+ // break;
+ // }
- var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);
+ case LegacyHiddenComponent:
+ case OffscreenComponent:
+ {
+ {
+ if (current.memoizedState !== null && current.memoizedState.cachePool !== null) {
+ var cache = current.memoizedState.cachePool.pool; // Retain/release the cache used for pending (suspended) nodes.
+ // Note that this is only reached in the non-suspended/visible case:
+ // when the content is suspended/hidden, the retain/release occurs
+ // via the parent Suspense component (see case above).
- if (shouldUpdate) {
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')) {
- if (typeof instance.componentWillMount === 'function') {
- instance.componentWillMount();
- }
+ if (cache != null) {
+ retainCache(cache);
+ }
+ }
+ }
- if (typeof instance.UNSAFE_componentWillMount === 'function') {
- instance.UNSAFE_componentWillMount();
+ break;
}
- }
- if (typeof instance.componentDidMount === 'function') {
- workInProgress.flags |= Update | LayoutStatic;
- }
+ case SuspenseComponent:
+ {
- if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
- workInProgress.flags |= MountLayoutDev;
- }
- } else {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidMount === 'function') {
- workInProgress.flags |= Update | LayoutStatic;
- }
+ break;
+ }
- if ((workInProgress.mode & StrictEffectsMode) !== NoMode) {
- workInProgress.flags |= MountLayoutDev;
- } // If shouldComponentUpdate returned false, we should still update the
- // memoized state to indicate that this work can be reused.
+ case CacheComponent:
+ {
+ {
+ var _cache = current.memoizedState.cache;
+ releaseCache(_cache);
+ }
+ break;
+ }
+ }
+}
- workInProgress.memoizedProps = newProps;
- workInProgress.memoizedState = newState;
- } // Update the existing instance's state, props, and context pointers even
- // if shouldComponentUpdate returns false.
+var hasBadMapPolyfill;
+{
+ hasBadMapPolyfill = false;
- instance.props = newProps;
- instance.state = newState;
- instance.context = nextContext;
- return shouldUpdate;
-} // Invokes the update life-cycles and returns false if it shouldn't rerender.
+ try {
+ var nonExtensibleObject = Object.preventExtensions({});
+ /* eslint-disable no-new */
+
+ new Map([[nonExtensibleObject, null]]);
+ new Set([nonExtensibleObject]);
+ /* eslint-enable no-new */
+ } catch (e) {
+ // TODO: Consider warning about bad polyfills
+ hasBadMapPolyfill = true;
+ }
+}
+function FiberNode(tag, pendingProps, key, mode) {
+ // Instance
+ this.tag = tag;
+ this.key = key;
+ this.elementType = null;
+ this.type = null;
+ this.stateNode = null; // Fiber
-function updateClassInstance(current, workInProgress, ctor, newProps, renderLanes) {
- var instance = workInProgress.stateNode;
- cloneUpdateQueue(current, workInProgress);
- var unresolvedOldProps = workInProgress.memoizedProps;
- var oldProps = resolveClassComponentProps(ctor, unresolvedOldProps);
- instance.props = oldProps;
- var unresolvedNewProps = workInProgress.pendingProps;
- var oldContext = instance.context;
- var contextType = ctor.contextType;
- var nextContext = emptyContextObject;
+ this.return = null;
+ this.child = null;
+ this.sibling = null;
+ this.index = 0;
+ this.ref = null;
+ this.refCleanup = null;
+ this.pendingProps = pendingProps;
+ this.memoizedProps = null;
+ this.updateQueue = null;
+ this.memoizedState = null;
+ this.dependencies = null;
+ this.mode = mode; // Effects
- if (typeof contextType === 'object' && contextType !== null) {
- nextContext = readContext(contextType);
- }
+ this.flags = NoFlags$1;
+ this.subtreeFlags = NoFlags$1;
+ this.deletions = null;
+ this.lanes = NoLanes;
+ this.childLanes = NoLanes;
+ this.alternate = null;
- var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
- var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what
- // ever the previously attempted to render - not the "current". However,
- // during componentDidUpdate we pass the "current" props.
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
+ {
+ // Note: The following is done to avoid a v8 performance cliff.
+ //
+ // Initializing the fields below to smis and later updating them with
+ // double values will cause Fibers to end up having separate shapes.
+ // This behavior/bug has something to do with Object.preventExtension().
+ // Fortunately this only impacts DEV builds.
+ // Unfortunately it makes React unusably slow for some applications.
+ // To work around this, initialize the fields below with doubles.
+ //
+ // Learn more about this here:
+ // https://github.com/facebook/react/issues/14365
+ // https://bugs.chromium.org/p/v8/issues/detail?id=8538
+ this.actualDuration = Number.NaN;
+ this.actualStartTime = Number.NaN;
+ this.selfBaseDuration = Number.NaN;
+ this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization.
+ // This won't trigger the performance cliff mentioned above,
+ // and it simplifies other profiler code (including DevTools).
- if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) {
- if (unresolvedOldProps !== unresolvedNewProps || oldContext !== nextContext) {
- callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext);
- }
+ this.actualDuration = 0;
+ this.actualStartTime = -1;
+ this.selfBaseDuration = 0;
+ this.treeBaseDuration = 0;
}
- resetHasForceUpdateBeforeProcessing();
- var oldState = workInProgress.memoizedState;
- var newState = instance.state = oldState;
- processUpdateQueue(workInProgress, newProps, instance, renderLanes);
- suspendIfUpdateReadFromEntangledAsyncAction();
- newState = workInProgress.memoizedState;
-
- if (unresolvedOldProps === unresolvedNewProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing() && !(enableLazyContextPropagation )) {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidUpdate === 'function') {
- if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) {
- workInProgress.flags |= Update;
- }
- }
+ {
+ // This isn't directly used but is handy for debugging internals:
+ this._debugInfo = null;
+ this._debugOwner = null;
+ this._debugNeedsRemount = false;
+ this._debugHookTypes = null;
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) {
- workInProgress.flags |= Snapshot;
- }
+ if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
+ Object.preventExtensions(this);
}
-
- return false;
}
+} // This is a constructor function, rather than a POJO constructor, still
+// please ensure we do the following:
+// 1) Nobody should add any instance methods on this. Instance methods can be
+// more difficult to predict when they get optimized and they are almost
+// never inlined properly in static compilers.
+// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
+// always know when it is a fiber.
+// 3) We might want to experiment with using numeric keys since they are easier
+// to optimize in a non-JIT environment.
+// 4) We can easily go from a constructor to a createFiber object literal if that
+// is faster.
+// 5) It should be easy to port this to a C struct and keep a C implementation
+// compatible.
- if (typeof getDerivedStateFromProps === 'function') {
- applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
- newState = workInProgress.memoizedState;
- }
- var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) || // TODO: In some cases, we'll end up checking if context has changed twice,
- // both before and after `shouldComponentUpdate` has been called. Not ideal,
- // but I'm loath to refactor this function. This only happens for memoized
- // components so it's not that common.
- enableLazyContextPropagation ;
+function createFiber(tag, pendingProps, key, mode) {
+ // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
+ return new FiberNode(tag, pendingProps, key, mode);
+}
+
+function shouldConstruct(Component) {
+ var prototype = Component.prototype;
+ return !!(prototype && prototype.isReactComponent);
+}
- if (shouldUpdate) {
- // In order to support react-lifecycles-compat polyfilled components,
- // Unsafe lifecycles should not be invoked for components using the new APIs.
- if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) {
- if (typeof instance.componentWillUpdate === 'function') {
- instance.componentWillUpdate(newProps, newState, nextContext);
- }
+function isSimpleFunctionComponent(type) {
+ return typeof type === 'function' && !shouldConstruct(type) && type.defaultProps === undefined;
+}
+function isFunctionClassComponent(type) {
+ return shouldConstruct(type);
+} // This is used to create an alternate fiber to do work on.
- if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
- instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
- }
- }
+function createWorkInProgress(current, pendingProps) {
+ var workInProgress = current.alternate;
- if (typeof instance.componentDidUpdate === 'function') {
- workInProgress.flags |= Update;
- }
+ if (workInProgress === null) {
+ // We use a double buffering pooling technique because we know that we'll
+ // only ever need at most two versions of a tree. We pool the "other" unused
+ // node that we're free to reuse. This is lazily created to avoid allocating
+ // extra objects for things that are never updated. It also allow us to
+ // reclaim the extra memory if needed.
+ workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
+ workInProgress.elementType = current.elementType;
+ workInProgress.type = current.type;
+ workInProgress.stateNode = current.stateNode;
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- workInProgress.flags |= Snapshot;
+ {
+ // DEV-only fields
+ workInProgress._debugOwner = current._debugOwner;
+ workInProgress._debugHookTypes = current._debugHookTypes;
}
+
+ workInProgress.alternate = current;
+ current.alternate = workInProgress;
} else {
- // If an update was already in progress, we should schedule an Update
- // effect even though we're bailing out, so that cWU/cDU are called.
- if (typeof instance.componentDidUpdate === 'function') {
- if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) {
- workInProgress.flags |= Update;
- }
- }
+ workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
- if (typeof instance.getSnapshotBeforeUpdate === 'function') {
- if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) {
- workInProgress.flags |= Snapshot;
- }
- } // If shouldComponentUpdate returned false, we should still update the
- // memoized props/state to indicate that this work can be reused.
+ workInProgress.type = current.type; // We already have an alternate.
+ // Reset the effect tag.
+ workInProgress.flags = NoFlags$1; // The effects are no longer valid.
- workInProgress.memoizedProps = newProps;
- workInProgress.memoizedState = newState;
- } // Update the existing instance's state, props, and context pointers even
- // if shouldComponentUpdate returns false.
+ workInProgress.subtreeFlags = NoFlags$1;
+ workInProgress.deletions = null;
+ {
+ // We intentionally reset, rather than copy, actualDuration & actualStartTime.
+ // This prevents time from endlessly accumulating in new commits.
+ // This has the downside of resetting values for different priority renders,
+ // But works for yielding (the common case) and should support resuming.
+ workInProgress.actualDuration = 0;
+ workInProgress.actualStartTime = -1;
+ }
+ } // Reset all effects except static ones.
+ // Static effects are not specific to a render.
- instance.props = newProps;
- instance.state = newState;
- instance.context = nextContext;
- return shouldUpdate;
-}
-function resolveClassComponentProps(Component, baseProps, // Only resolve default props if this is a lazy component. Otherwise, they
-// would have already been resolved by the JSX runtime.
-// TODO: We're going to remove default prop resolution from the JSX runtime
-// and keep it only for class components. As part of that change, we should
-// remove this extra check.
-alreadyResolvedDefaultProps) {
- var newProps = baseProps;
+ workInProgress.flags = current.flags & StaticMask;
+ workInProgress.childLanes = current.childLanes;
+ workInProgress.lanes = current.lanes;
+ workInProgress.child = current.child;
+ workInProgress.memoizedProps = current.memoizedProps;
+ workInProgress.memoizedState = current.memoizedState;
+ workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
+ // it cannot be shared with the current fiber.
- {
- // Remove ref from the props object, if it exists.
- if ('ref' in baseProps) {
- newProps = {};
+ var currentDependencies = current.dependencies;
+ workInProgress.dependencies = currentDependencies === null ? null : {
+ lanes: currentDependencies.lanes,
+ firstContext: currentDependencies.firstContext
+ }; // These will be overridden during the parent's reconciliation
- for (var propName in baseProps) {
- if (propName !== 'ref') {
- newProps[propName] = baseProps[propName];
- }
- }
- }
- } // Resolve default props.
+ workInProgress.sibling = current.sibling;
+ workInProgress.index = current.index;
+ workInProgress.ref = current.ref;
+ workInProgress.refCleanup = current.refCleanup;
+ {
+ workInProgress.selfBaseDuration = current.selfBaseDuration;
+ workInProgress.treeBaseDuration = current.treeBaseDuration;
+ }
- var defaultProps = Component.defaultProps;
+ {
+ workInProgress._debugInfo = current._debugInfo;
+ workInProgress._debugNeedsRemount = current._debugNeedsRemount;
- if (defaultProps && ( // If disableDefaultPropsExceptForClasses is true, we always resolve
- // default props here in the reconciler, rather than in the JSX runtime.
- disableDefaultPropsExceptForClasses )) {
- // We may have already copied the props object above to remove ref. If so,
- // we can modify that. Otherwise, copy the props object with Object.assign.
- if (newProps === baseProps) {
- newProps = assign({}, newProps);
- } // Taken from old JSX runtime, where this used to live.
+ switch (workInProgress.tag) {
+ case FunctionComponent:
+ case SimpleMemoComponent:
+ workInProgress.type = resolveFunctionForHotReloading(current.type);
+ break;
+ case ClassComponent:
+ workInProgress.type = resolveClassForHotReloading(current.type);
+ break;
- for (var _propName in defaultProps) {
- if (newProps[_propName] === undefined) {
- newProps[_propName] = defaultProps[_propName];
- }
+ case ForwardRef:
+ workInProgress.type = resolveForwardRefForHotReloading(current.type);
+ break;
}
}
- return newProps;
-}
+ return workInProgress;
+} // Used to reuse a Fiber for a second pass.
-var didWarnAboutUndefinedSnapshotBeforeUpdate = null;
+function resetWorkInProgress(workInProgress, renderLanes) {
+ // This resets the Fiber to what createFiber or createWorkInProgress would
+ // have set the values to before during the first pass. Ideally this wouldn't
+ // be necessary but unfortunately many code paths reads from the workInProgress
+ // when they should be reading from current and writing to workInProgress.
+ // We assume pendingProps, index, key, ref, return are still untouched to
+ // avoid doing another reconciliation.
+ // Reset the effect flags but keep any Placement tags, since that's something
+ // that child fiber is setting, not the reconciliation.
+ workInProgress.flags &= StaticMask | Placement; // The effects are no longer valid.
-{
- didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
-} // Used during the commit phase to track the state of the Offscreen component stack.
-// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
+ var current = workInProgress.alternate;
+ if (current === null) {
+ // Reset to createFiber's initial values.
+ workInProgress.childLanes = NoLanes;
+ workInProgress.lanes = renderLanes;
+ workInProgress.child = null;
+ workInProgress.subtreeFlags = NoFlags$1;
+ workInProgress.memoizedProps = null;
+ workInProgress.memoizedState = null;
+ workInProgress.updateQueue = null;
+ workInProgress.dependencies = null;
+ workInProgress.stateNode = null;
-var offscreenSubtreeIsHidden = false;
-var offscreenSubtreeWasHidden = false; // Used to track if a form needs to be reset at the end of the mutation phase.
+ {
+ // Note: We don't reset the actualTime counts. It's useful to accumulate
+ // actual time across multiple render passes.
+ workInProgress.selfBaseDuration = 0;
+ workInProgress.treeBaseDuration = 0;
+ }
+ } else {
+ // Reset to the cloned values that createWorkInProgress would've.
+ workInProgress.childLanes = current.childLanes;
+ workInProgress.lanes = current.lanes;
+ workInProgress.child = current.child;
+ workInProgress.subtreeFlags = NoFlags$1;
+ workInProgress.deletions = null;
+ workInProgress.memoizedProps = current.memoizedProps;
+ workInProgress.memoizedState = current.memoizedState;
+ workInProgress.updateQueue = current.updateQueue; // Needed because Blocks store data on type.
-var needsFormReset = false;
-var PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
-var nextEffect = null; // Used for Profiling builds to track updaters.
+ workInProgress.type = current.type; // Clone the dependencies object. This is mutated during the render phase, so
+ // it cannot be shared with the current fiber.
-var inProgressLanes = null;
-var inProgressRoot = null;
+ var currentDependencies = current.dependencies;
+ workInProgress.dependencies = currentDependencies === null ? null : {
+ lanes: currentDependencies.lanes,
+ firstContext: currentDependencies.firstContext
+ };
-function shouldProfile(current) {
- return (current.mode & ProfileMode) !== NoMode && (getExecutionContext() & CommitContext) !== NoContext;
+ {
+ // Note: We don't reset the actualTime counts. It's useful to accumulate
+ // actual time across multiple render passes.
+ workInProgress.selfBaseDuration = current.selfBaseDuration;
+ workInProgress.treeBaseDuration = current.treeBaseDuration;
+ }
+ }
+
+ return workInProgress;
}
+function createHostRootFiber(tag, isStrictMode, concurrentUpdatesByDefaultOverride) {
+ var mode;
-function callComponentWillUnmountWithTimer(current, instance) {
- instance.props = resolveClassComponentProps(current.type, current.memoizedProps);
- instance.state = current.memoizedState;
+ {
+ mode = ConcurrentMode;
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- instance.componentWillUnmount();
- } finally {
- recordLayoutEffectDuration(current);
+ if (isStrictMode === true) {
+ mode |= StrictLegacyMode | StrictEffectsMode;
}
- } else {
- instance.componentWillUnmount();
}
-} // Capture errors so they don't interrupt unmounting.
-
-function safelyCallComponentWillUnmount(current, nearestMountedAncestor, instance) {
- try {
- callComponentWillUnmountWithTimer(current, instance);
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
+ if (isDevToolsPresent) {
+ // Always collect profile timings when DevTools are present.
+ // This enables DevTools to start capturing timing at any point–
+ // Without some nodes in the tree having empty base times.
+ mode |= ProfileMode;
}
-} // Capture errors so they don't interrupt mounting.
-
-function safelyAttachRef(current, nearestMountedAncestor) {
- try {
- commitAttachRef(current);
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
+ return createFiber(HostRoot, null, null, mode);
}
+function createFiberFromTypeAndProps(type, // React$ElementType
+key, pendingProps, owner, mode, lanes) {
+ var fiberTag = FunctionComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
-function safelyDetachRef(current, nearestMountedAncestor) {
- var ref = current.ref;
- var refCleanup = current.refCleanup;
+ var resolvedType = type;
- if (ref !== null) {
- if (typeof refCleanup === 'function') {
- try {
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- refCleanup();
- } finally {
- recordLayoutEffectDuration(current);
- }
- } else {
- refCleanup();
- }
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- } finally {
- // `refCleanup` has been called. Nullify all references to it to prevent double invocation.
- current.refCleanup = null;
- var finishedWork = current.alternate;
+ if (typeof type === 'function') {
+ if (shouldConstruct(type)) {
+ fiberTag = ClassComponent;
- if (finishedWork != null) {
- finishedWork.refCleanup = null;
- }
- }
- } else if (typeof ref === 'function') {
- try {
- if (shouldProfile(current)) {
- try {
- startLayoutEffectTimer();
- ref(null);
- } finally {
- recordLayoutEffectDuration(current);
- }
- } else {
- ref(null);
- }
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
+ {
+ resolvedType = resolveClassForHotReloading(resolvedType);
}
} else {
- // $FlowFixMe[incompatible-use] unable to narrow type to RefObject
- ref.current = null;
+ {
+ resolvedType = resolveFunctionForHotReloading(resolvedType);
+ }
}
- }
-}
+ } else if (typeof type === 'string') {
+ {
+ var hostContext = getHostContext();
+ fiberTag = isHostHoistableType(type, pendingProps, hostContext) ? HostHoistable : isHostSingletonType(type) ? HostSingleton : HostComponent;
+ }
+ } else {
+ getTag: switch (type) {
+ case REACT_FRAGMENT_TYPE:
+ return createFiberFromFragment(pendingProps.children, mode, lanes, key);
-function safelyCallDestroy(current, nearestMountedAncestor, destroy) {
- try {
- destroy();
- } catch (error) {
- captureCommitPhaseError(current, nearestMountedAncestor, error);
- }
-}
-var shouldFireAfterActiveInstanceBlur = false;
-function commitBeforeMutationEffects(root, firstChild) {
- prepareForCommit();
- nextEffect = firstChild;
- commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber
+ case REACT_STRICT_MODE_TYPE:
+ fiberTag = Mode;
+ mode |= StrictLegacyMode;
- var shouldFire = shouldFireAfterActiveInstanceBlur;
- shouldFireAfterActiveInstanceBlur = false;
- return shouldFire;
-}
+ {
+ // Strict effects should never run on legacy roots
+ mode |= StrictEffectsMode;
+ }
-function commitBeforeMutationEffects_begin() {
- while (nextEffect !== null) {
- var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.
+ break;
- var child = fiber.child;
+ case REACT_PROFILER_TYPE:
+ return createFiberFromProfiler(pendingProps, mode, lanes, key);
- if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags$1 && child !== null) {
- child.return = fiber;
- nextEffect = child;
- } else {
- commitBeforeMutationEffects_complete();
- }
- }
-}
+ case REACT_SUSPENSE_TYPE:
+ return createFiberFromSuspense(pendingProps, mode, lanes, key);
-function commitBeforeMutationEffects_complete() {
- while (nextEffect !== null) {
- var fiber = nextEffect;
- setCurrentFiber(fiber);
+ case REACT_SUSPENSE_LIST_TYPE:
+ return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
- try {
- commitBeforeMutationEffectsOnFiber(fiber);
- } catch (error) {
- captureCommitPhaseError(fiber, fiber.return, error);
- }
+ case REACT_OFFSCREEN_TYPE:
+ return createFiberFromOffscreen(pendingProps, mode, lanes, key);
- resetCurrentFiber();
- var sibling = fiber.sibling;
+ case REACT_LEGACY_HIDDEN_TYPE:
- if (sibling !== null) {
- sibling.return = fiber.return;
- nextEffect = sibling;
- return;
- }
+ // Fall through
- nextEffect = fiber.return;
- }
-}
+ case REACT_SCOPE_TYPE:
-function commitBeforeMutationEffectsOnFiber(finishedWork) {
- var current = finishedWork.alternate;
- var flags = finishedWork.flags;
+ // Fall through
- if ((flags & Snapshot) !== NoFlags$1) {
- setCurrentFiber(finishedWork);
- }
+ case REACT_TRACING_MARKER_TYPE:
- switch (finishedWork.tag) {
- case FunctionComponent:
- {
- {
- if ((flags & Update) !== NoFlags$1) {
- commitUseEffectEventMount(finishedWork);
- }
- }
+ // Fall through
- break;
- }
+ case REACT_DEBUG_TRACING_MODE_TYPE:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- break;
- }
+ // Fall through
- case ClassComponent:
- {
- if ((flags & Snapshot) !== NoFlags$1) {
- if (current !== null) {
- var prevProps = current.memoizedProps;
- var prevState = current.memoizedState;
- var instance = finishedWork.stateNode; // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
+ default:
+ {
+ if (typeof type === 'object' && type !== null) {
+ switch (type.$$typeof) {
+ case REACT_PROVIDER_TYPE:
- {
- if (!finishedWork.type.defaultProps && !('ref' in finishedWork.memoizedProps) && !didWarnAboutReassigningProps) {
- if (instance.props !== finishedWork.memoizedProps) {
- error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
+ // Fall through
+
+ case REACT_CONTEXT_TYPE:
+ {
+ fiberTag = ContextProvider;
+ break getTag;
}
- if (instance.state !== finishedWork.memoizedState) {
- error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
+ case REACT_CONSUMER_TYPE:
+ {
+ fiberTag = ContextConsumer;
+ break getTag;
}
- }
- }
- var snapshot = instance.getSnapshotBeforeUpdate(resolveClassComponentProps(finishedWork.type, prevProps), prevState);
+ // Fall through
- {
- var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
+ case REACT_FORWARD_REF_TYPE:
+ fiberTag = ForwardRef;
- if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
- didWarnSet.add(finishedWork.type);
+ {
+ resolvedType = resolveForwardRefForHotReloading(resolvedType);
+ }
- error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
- }
- }
+ break getTag;
- instance.__reactInternalSnapshotBeforeUpdate = snapshot;
+ case REACT_MEMO_TYPE:
+ fiberTag = MemoComponent;
+ break getTag;
+
+ case REACT_LAZY_TYPE:
+ fiberTag = LazyComponent;
+ resolvedType = null;
+ break getTag;
+ }
}
- }
- break;
- }
+ var info = '';
- case HostRoot:
- {
- if ((flags & Snapshot) !== NoFlags$1) {
{
- var root = finishedWork.stateNode;
- clearContainer(root.containerInfo);
- }
- }
-
- break;
- }
+ if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
+ info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and " + 'named imports.';
+ }
- case HostComponent:
- case HostHoistable:
- case HostSingleton:
- case HostText:
- case HostPortal:
- case IncompleteClassComponent:
- // Nothing to do for these component types
- break;
+ var ownerName = owner ? getComponentNameFromOwner(owner) : null;
- default:
- {
- if ((flags & Snapshot) !== NoFlags$1) {
- throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.');
+ if (ownerName) {
+ info += '\n\nCheck the render method of `' + ownerName + '`.';
+ }
+ }
+
+ throw new Error('Element type is invalid: expected a string (for built-in ' + 'components) or a class/function (for composite components) ' + ("but got: " + (type == null ? type : typeof type) + "." + info));
}
- }
+ }
}
- if ((flags & Snapshot) !== NoFlags$1) {
- resetCurrentFiber();
+ var fiber = createFiber(fiberTag, pendingProps, key, mode);
+ fiber.elementType = type;
+ fiber.type = resolvedType;
+ fiber.lanes = lanes;
+
+ {
+ fiber._debugOwner = owner;
}
+
+ return fiber;
}
+function createFiberFromElement(element, mode, lanes) {
+ var owner = null;
-function commitHookEffectListUnmount(flags, finishedWork, nearestMountedAncestor) {
- var updateQueue = finishedWork.updateQueue;
- var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
+ {
+ owner = element._owner;
+ }
- if (lastEffect !== null) {
- var firstEffect = lastEffect.next;
- var effect = firstEffect;
+ var type = element.type;
+ var key = element.key;
+ var pendingProps = element.props;
+ var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
- do {
- if ((effect.tag & flags) === flags) {
- // Unmount
- var inst = effect.inst;
- var destroy = inst.destroy;
+ {
+ fiber._debugOwner = element._owner;
+ }
- if (destroy !== undefined) {
- inst.destroy = undefined;
+ return fiber;
+}
+function createFiberFromFragment(elements, mode, lanes, key) {
+ var fiber = createFiber(Fragment, elements, key, mode);
+ fiber.lanes = lanes;
+ return fiber;
+}
- {
- if ((flags & Passive) !== NoFlags) {
- markComponentPassiveEffectUnmountStarted(finishedWork);
- } else if ((flags & Layout) !== NoFlags) {
- markComponentLayoutEffectUnmountStarted(finishedWork);
- }
- }
+function createFiberFromProfiler(pendingProps, mode, lanes, key) {
+ {
+ if (typeof pendingProps.id !== 'string') {
+ error('Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.', typeof pendingProps.id);
+ }
+ }
- {
- if ((flags & Insertion) !== NoFlags) {
- setIsRunningInsertionEffect(true);
- }
- }
+ var fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
+ fiber.elementType = REACT_PROFILER_TYPE;
+ fiber.lanes = lanes;
- safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
+ {
+ fiber.stateNode = {
+ effectDuration: 0,
+ passiveEffectDuration: 0
+ };
+ }
- {
- if ((flags & Insertion) !== NoFlags) {
- setIsRunningInsertionEffect(false);
- }
- }
+ return fiber;
+}
- {
- if ((flags & Passive) !== NoFlags) {
- markComponentPassiveEffectUnmountStopped();
- } else if ((flags & Layout) !== NoFlags) {
- markComponentLayoutEffectUnmountStopped();
- }
- }
- }
- }
+function createFiberFromSuspense(pendingProps, mode, lanes, key) {
+ var fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
+ fiber.elementType = REACT_SUSPENSE_TYPE;
+ fiber.lanes = lanes;
+ return fiber;
+}
+function createFiberFromSuspenseList(pendingProps, mode, lanes, key) {
+ var fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
+ fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
+ fiber.lanes = lanes;
+ return fiber;
+}
+function createFiberFromOffscreen(pendingProps, mode, lanes, key) {
+ var fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
+ fiber.elementType = REACT_OFFSCREEN_TYPE;
+ fiber.lanes = lanes;
+ var primaryChildInstance = {
+ _visibility: OffscreenVisible,
+ _pendingVisibility: OffscreenVisible,
+ _pendingMarkers: null,
+ _retryCache: null,
+ _transitions: null,
+ _current: null,
+ detach: function () {
+ return detachOffscreenInstance(primaryChildInstance);
+ },
+ attach: function () {
+ return attachOffscreenInstance(primaryChildInstance);
+ }
+ };
+ fiber.stateNode = primaryChildInstance;
+ return fiber;
+}
+function createFiberFromText(content, mode, lanes) {
+ var fiber = createFiber(HostText, content, null, mode);
+ fiber.lanes = lanes;
+ return fiber;
+}
+function createFiberFromDehydratedFragment(dehydratedNode) {
+ var fiber = createFiber(DehydratedFragment, null, null, NoMode);
+ fiber.stateNode = dehydratedNode;
+ return fiber;
+}
+function createFiberFromPortal(portal, mode, lanes) {
+ var pendingProps = portal.children !== null ? portal.children : [];
+ var fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
+ fiber.lanes = lanes;
+ fiber.stateNode = {
+ containerInfo: portal.containerInfo,
+ pendingChildren: null,
+ // Used by persistent updates
+ implementation: portal.implementation
+ };
+ return fiber;
+}
- effect = effect.next;
- } while (effect !== firstEffect);
- }
+// This is imported by the event replaying implementation in React DOM. It's
+// in a separate file to break a circular dependency between the renderer and
+// the reconciler.
+function isRootDehydrated(root) {
+ var currentState = root.current.memoizedState;
+ return currentState.isDehydrated;
}
-function commitHookEffectListMount(flags, finishedWork) {
- var updateQueue = finishedWork.updateQueue;
- var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
+/**
+ * Tag the fiber with an update effect. This turns a Placement into
+ * a PlacementAndUpdate.
+ */
- if (lastEffect !== null) {
- var firstEffect = lastEffect.next;
- var effect = firstEffect;
+function markUpdate(workInProgress) {
+ workInProgress.flags |= Update;
+}
- do {
- if ((effect.tag & flags) === flags) {
- {
- if ((flags & Passive) !== NoFlags) {
- markComponentPassiveEffectMountStarted(finishedWork);
- } else if ((flags & Layout) !== NoFlags) {
- markComponentLayoutEffectMountStarted(finishedWork);
- }
- } // Mount
+function appendAllChildren(parent, workInProgress, needsVisibilityToggle, isHidden) {
+ {
+ // We only have the top Fiber that was created but we need recurse down its
+ // children to find all the terminal nodes.
+ var node = workInProgress.child;
+ while (node !== null) {
+ if (node.tag === HostComponent || node.tag === HostText) {
+ appendInitialChild(parent, node.stateNode);
+ } else if (node.tag === HostPortal || (node.tag === HostSingleton )) ; else if (node.child !== null) {
+ node.child.return = node;
+ node = node.child;
+ continue;
+ }
- var create = effect.create;
+ if (node === workInProgress) {
+ return;
+ } // $FlowFixMe[incompatible-use] found when upgrading Flow
- {
- if ((flags & Insertion) !== NoFlags) {
- setIsRunningInsertionEffect(true);
- }
+
+ while (node.sibling === null) {
+ // $FlowFixMe[incompatible-use] found when upgrading Flow
+ if (node.return === null || node.return === workInProgress) {
+ return;
}
- var inst = effect.inst;
- var destroy = create();
- inst.destroy = destroy;
+ node = node.return;
+ } // $FlowFixMe[incompatible-use] found when upgrading Flow
- {
- if ((flags & Insertion) !== NoFlags) {
- setIsRunningInsertionEffect(false);
- }
- }
- {
- if ((flags & Passive) !== NoFlags) {
- markComponentPassiveEffectMountStopped();
- } else if ((flags & Layout) !== NoFlags) {
- markComponentLayoutEffectMountStopped();
- }
- }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ }
+ }
+} // An unfortunate fork of appendAllChildren because we have two different parent types.
- {
- if (destroy !== undefined && typeof destroy !== 'function') {
- var hookName = void 0;
+function updateHostComponent(current, workInProgress, type, newProps, renderLanes) {
+ {
+ // If we have an alternate, that means this is an update and we need to
+ // schedule a side-effect to do the updates.
+ var oldProps = current.memoizedProps;
+
+ if (oldProps === newProps) {
+ // In mutation mode, this is sufficient for a bailout because
+ // we won't touch this node even if children changed.
+ return;
+ }
+
+ markUpdate(workInProgress);
+ }
+} // This function must be called at the very end of the complete phase, because
+// it might throw to suspend, and if the resource immediately loads, the work
+// loop will resume rendering as if the work-in-progress completed. So it must
+// fully complete.
+// TODO: This should ideally move to begin phase, but currently the instance is
+// not created until the complete phase. For our existing use cases, host nodes
+// that suspend don't have children, so it doesn't matter. But that might not
+// always be true in the future.
+
+
+function preloadInstanceAndSuspendIfNeeded(workInProgress, type, props, renderLanes) {
+ {
+ // If this flag was set previously, we can remove it. The flag
+ // represents whether this particular set of props might ever need to
+ // suspend. The safest thing to do is for maySuspendCommit to always
+ // return true, but if the renderer is reasonably confident that the
+ // underlying resource won't be evicted, it can return false as a
+ // performance optimization.
+ workInProgress.flags &= ~MaySuspendCommit;
+ return;
+ } // Mark this fiber with a flag. This gets set on all host instances
+}
- if ((effect.tag & Layout) !== NoFlags$1) {
- hookName = 'useLayoutEffect';
- } else if ((effect.tag & Insertion) !== NoFlags$1) {
- hookName = 'useInsertionEffect';
- } else {
- hookName = 'useEffect';
- }
+function preloadResourceAndSuspendIfNeeded(workInProgress, resource, type, props, renderLanes) {
+ // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
+ if (!mayResourceSuspendCommit(resource)) {
+ workInProgress.flags &= ~MaySuspendCommit;
+ return;
+ }
- var addendum = void 0;
+ workInProgress.flags |= MaySuspendCommit;
+ var rootRenderLanes = getWorkInProgressRootRenderLanes();
- if (destroy === null) {
- addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).';
- } else if (typeof destroy.then === 'function') {
- addendum = '\n\nIt looks like you wrote ' + hookName + '(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + hookName + '(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching';
- } else {
- addendum = ' You returned: ' + destroy;
- }
+ if (!includesOnlyNonUrgentLanes(rootRenderLanes)) ; else {
+ var isReady = preloadResource(resource);
- error('%s must not return anything besides a function, ' + 'which is used for clean-up.%s', hookName, addendum);
- }
- }
+ if (!isReady) {
+ if (shouldRemainOnPreviousScreen()) {
+ workInProgress.flags |= ShouldSuspendCommit;
+ } else {
+ suspendCommit();
}
-
- effect = effect.next;
- } while (effect !== firstEffect);
+ }
}
}
-function commitUseEffectEventMount(finishedWork) {
- var updateQueue = finishedWork.updateQueue;
- var eventPayloads = updateQueue !== null ? updateQueue.events : null;
+function scheduleRetryEffect(workInProgress, retryQueue) {
+ var wakeables = retryQueue;
- if (eventPayloads !== null) {
- for (var ii = 0; ii < eventPayloads.length; ii++) {
- var _eventPayloads$ii = eventPayloads[ii],
- ref = _eventPayloads$ii.ref,
- nextImpl = _eventPayloads$ii.nextImpl;
- ref.impl = nextImpl;
+ if (wakeables !== null) {
+ // Schedule an effect to attach a retry listener to the promise.
+ // TODO: Move to passive phase
+ workInProgress.flags |= Update;
+ } else {
+ // This boundary suspended, but no wakeables were added to the retry
+ // queue. Check if the renderer suspended commit. If so, this means
+ // that once the fallback is committed, we can immediately retry
+ // rendering again, because rendering wasn't actually blocked. Only
+ // the commit phase.
+ // TODO: Consider a model where we always schedule an immediate retry, even
+ // for normal Suspense. That way the retry can partially render up to the
+ // first thing that suspends.
+ if (workInProgress.flags & ScheduleRetry) {
+ var retryLane = // TODO: This check should probably be moved into claimNextRetryLane
+ // I also suspect that we need some further consolidation of offscreen
+ // and retry lanes.
+ workInProgress.tag !== OffscreenComponent ? claimNextRetryLane() : OffscreenLane;
+ workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
}
}
}
-function commitPassiveEffectDurations(finishedRoot, finishedWork) {
- if (getExecutionContext() & CommitContext) {
- // Only Profilers with work in their subtree will have an Update effect scheduled.
- if ((finishedWork.flags & Update) !== NoFlags$1) {
- switch (finishedWork.tag) {
- case Profiler:
- {
- var passiveEffectDuration = finishedWork.stateNode.passiveEffectDuration;
- var _finishedWork$memoize = finishedWork.memoizedProps,
- id = _finishedWork$memoize.id,
- onPostCommit = _finishedWork$memoize.onPostCommit; // This value will still reflect the previous commit phase.
- // It does not get reset until the start of the next commit phase.
+function updateHostText(current, workInProgress, oldText, newText) {
+ {
+ // If the text differs, mark it as an update. All the work in done in commitWork.
+ if (oldText !== newText) {
+ markUpdate(workInProgress);
+ }
+ }
+}
- var commitTime = getCommitTime();
- var phase = finishedWork.alternate === null ? 'mount' : 'update';
+function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) {
+ if (getIsHydrating()) {
+ // If we're hydrating, we should consume as many items as we can
+ // so we don't leave any behind.
+ return;
+ }
- {
- if (isCurrentUpdateNested()) {
- phase = 'nested-update';
- }
- }
+ switch (renderState.tailMode) {
+ case 'hidden':
+ {
+ // Any insertions at the end of the tail list after this point
+ // should be invisible. If there are already mounted boundaries
+ // anything before them are not considered for collapsing.
+ // Therefore we need to go through the whole tail to find if
+ // there are any.
+ var tailNode = renderState.tail;
+ var lastTailNode = null;
- if (typeof onPostCommit === 'function') {
- onPostCommit(id, phase, passiveEffectDuration, commitTime);
- } // Bubble times to the next nearest ancestor Profiler.
- // After we process that Profiler, we'll bubble further up.
+ while (tailNode !== null) {
+ if (tailNode.alternate !== null) {
+ lastTailNode = tailNode;
+ }
+ tailNode = tailNode.sibling;
+ } // Next we're simply going to delete all insertions after the
+ // last rendered item.
- var parentFiber = finishedWork.return;
- outer: while (parentFiber !== null) {
- switch (parentFiber.tag) {
- case HostRoot:
- var root = parentFiber.stateNode;
- root.passiveEffectDuration += passiveEffectDuration;
- break outer;
+ if (lastTailNode === null) {
+ // All remaining items in the tail are insertions.
+ renderState.tail = null;
+ } else {
+ // Detach the insertion after the last node that was already
+ // inserted.
+ lastTailNode.sibling = null;
+ }
- case Profiler:
- var parentStateNode = parentFiber.stateNode;
- parentStateNode.passiveEffectDuration += passiveEffectDuration;
- break outer;
- }
+ break;
+ }
- parentFiber = parentFiber.return;
- }
+ case 'collapsed':
+ {
+ // Any insertions at the end of the tail list after this point
+ // should be invisible. If there are already mounted boundaries
+ // anything before them are not considered for collapsing.
+ // Therefore we need to go through the whole tail to find if
+ // there are any.
+ var _tailNode = renderState.tail;
+ var _lastTailNode = null;
- break;
+ while (_tailNode !== null) {
+ if (_tailNode.alternate !== null) {
+ _lastTailNode = _tailNode;
}
- }
- }
- }
-}
-
-function commitHookLayoutEffects(finishedWork, hookFlags) {
- // At this point layout effects have already been destroyed (during mutation phase).
- // This is done to prevent sibling component effects from interfering with each other,
- // e.g. a destroy function in one component should never override a ref set
- // by a create function in another component during the same commit.
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
+ _tailNode = _tailNode.sibling;
+ } // Next we're simply going to delete all insertions after the
+ // last rendered item.
-function commitClassLayoutLifecycles(finishedWork, current) {
- var instance = finishedWork.stateNode;
- if (current === null) {
- // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
- {
- if (!finishedWork.type.defaultProps && !('ref' in finishedWork.memoizedProps) && !didWarnAboutReassigningProps) {
- if (instance.props !== finishedWork.memoizedProps) {
- error('Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
+ if (_lastTailNode === null) {
+ // All remaining items in the tail are insertions.
+ if (!hasRenderedATailFallback && renderState.tail !== null) {
+ // We suspended during the head. We want to show at least one
+ // row at the tail. So we'll keep on and cut off the rest.
+ renderState.tail.sibling = null;
+ } else {
+ renderState.tail = null;
+ }
+ } else {
+ // Detach the insertion after the last node that was already
+ // inserted.
+ _lastTailNode.sibling = null;
}
- if (instance.state !== finishedWork.memoizedState) {
- error('Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
- }
+ break;
}
- }
+ }
+}
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+function bubbleProperties(completedWork) {
+ var didBailout = completedWork.alternate !== null && completedWork.alternate.child === completedWork.child;
+ var newChildLanes = NoLanes;
+ var subtreeFlags = NoFlags$1;
+
+ if (!didBailout) {
+ // Bubble up the earliest expiration time.
+ if ((completedWork.mode & ProfileMode) !== NoMode) {
+ // In profiling mode, resetChildExpirationTime is also used to reset
+ // profiler durations.
+ var actualDuration = completedWork.actualDuration;
+ var treeBaseDuration = completedWork.selfBaseDuration;
+ var child = completedWork.child;
+
+ while (child !== null) {
+ newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes));
+ subtreeFlags |= child.subtreeFlags;
+ subtreeFlags |= child.flags; // When a fiber is cloned, its actualDuration is reset to 0. This value will
+ // only be updated if work is done on the fiber (i.e. it doesn't bailout).
+ // When work is done, it should bubble to the parent's actualDuration. If
+ // the fiber has not been cloned though, (meaning no work was done), then
+ // this value will reflect the amount of time spent working on a previous
+ // render. In that case it should not bubble. We determine whether it was
+ // cloned by comparing the child pointer.
+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+
+ actualDuration += child.actualDuration; // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+
+ treeBaseDuration += child.treeBaseDuration;
+ child = child.sibling;
}
- recordLayoutEffectDuration(finishedWork);
+ completedWork.actualDuration = actualDuration;
+ completedWork.treeBaseDuration = treeBaseDuration;
} else {
- try {
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ var _child = completedWork.child;
+
+ while (_child !== null) {
+ newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child.lanes, _child.childLanes));
+ subtreeFlags |= _child.subtreeFlags;
+ subtreeFlags |= _child.flags; // Update the return pointer so the tree is consistent. This is a code
+ // smell because it assumes the commit phase is never concurrent with
+ // the render phase. Will address during refactor to alternate model.
+
+ _child.return = completedWork;
+ _child = _child.sibling;
}
}
+
+ completedWork.subtreeFlags |= subtreeFlags;
} else {
- var prevProps = resolveClassComponentProps(finishedWork.type, current.memoizedProps);
- var prevState = current.memoizedState; // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
+ // Bubble up the earliest expiration time.
+ if ((completedWork.mode & ProfileMode) !== NoMode) {
+ // In profiling mode, resetChildExpirationTime is also used to reset
+ // profiler durations.
+ var _treeBaseDuration = completedWork.selfBaseDuration;
+ var _child2 = completedWork.child;
- {
- if (!finishedWork.type.defaultProps && !('ref' in finishedWork.memoizedProps) && !didWarnAboutReassigningProps) {
- if (instance.props !== finishedWork.memoizedProps) {
- error('Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
- }
+ while (_child2 !== null) {
+ newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child2.lanes, _child2.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to,
+ // so we should bubble those up even during a bailout. All the other
+ // flags have a lifetime only of a single render + commit, so we should
+ // ignore them.
- if (instance.state !== finishedWork.memoizedState) {
- error('Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
- }
- }
- }
+ subtreeFlags |= _child2.subtreeFlags & StaticMask;
+ subtreeFlags |= _child2.flags & StaticMask; // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ _treeBaseDuration += _child2.treeBaseDuration;
+ _child2 = _child2.sibling;
}
- recordLayoutEffectDuration(finishedWork);
+ completedWork.treeBaseDuration = _treeBaseDuration;
} else {
- try {
- instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
-}
-
-function commitClassCallbacks(finishedWork) {
- // TODO: I think this is now always non-null by the time it reaches the
- // commit phase. Consider removing the type check.
- var updateQueue = finishedWork.updateQueue;
+ var _child3 = completedWork.child;
- if (updateQueue !== null) {
- var instance = finishedWork.stateNode;
+ while (_child3 !== null) {
+ newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child3.lanes, _child3.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to,
+ // so we should bubble those up even during a bailout. All the other
+ // flags have a lifetime only of a single render + commit, so we should
+ // ignore them.
- {
- if (!finishedWork.type.defaultProps && !('ref' in finishedWork.memoizedProps) && !didWarnAboutReassigningProps) {
- if (instance.props !== finishedWork.memoizedProps) {
- error('Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
- }
+ subtreeFlags |= _child3.subtreeFlags & StaticMask;
+ subtreeFlags |= _child3.flags & StaticMask; // Update the return pointer so the tree is consistent. This is a code
+ // smell because it assumes the commit phase is never concurrent with
+ // the render phase. Will address during refactor to alternate model.
- if (instance.state !== finishedWork.memoizedState) {
- error('Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
- }
+ _child3.return = completedWork;
+ _child3 = _child3.sibling;
}
- } // We could update instance props and state here,
- // but instead we rely on them being set during last render.
- // TODO: revisit this when we implement resuming.
-
-
- try {
- commitCallbacks(updateQueue, instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
- }
-}
-
-function commitHostComponentMount(finishedWork) {
- var type = finishedWork.type;
- var props = finishedWork.memoizedProps;
- var instance = finishedWork.stateNode;
- try {
- commitMount(instance, type, props, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ completedWork.subtreeFlags |= subtreeFlags;
}
-}
-function commitProfilerUpdate(finishedWork, current) {
- if (getExecutionContext() & CommitContext) {
- try {
- var _finishedWork$memoize2 = finishedWork.memoizedProps,
- onCommit = _finishedWork$memoize2.onCommit,
- onRender = _finishedWork$memoize2.onRender;
- var effectDuration = finishedWork.stateNode.effectDuration;
- var commitTime = getCommitTime();
- var phase = current === null ? 'mount' : 'update';
+ completedWork.childLanes = newChildLanes;
+ return didBailout;
+}
- if (enableProfilerNestedUpdatePhase) {
- if (isCurrentUpdateNested()) {
- phase = 'nested-update';
- }
- }
+function completeDehydratedSuspenseBoundary(current, workInProgress, nextState) {
+ var wasHydrated = popHydrationState(workInProgress);
- if (typeof onRender === 'function') {
- onRender(finishedWork.memoizedProps.id, phase, finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, commitTime);
+ if (nextState !== null && nextState.dehydrated !== null) {
+ // We might be inside a hydration state the first time we're picking up this
+ // Suspense boundary, and also after we've reentered it for further hydration.
+ if (current === null) {
+ if (!wasHydrated) {
+ throw new Error('A dehydrated suspense component was completed without a hydrated node. ' + 'This is probably a bug in React.');
}
- if (enableProfilerCommitHooks) {
- if (typeof onCommit === 'function') {
- onCommit(finishedWork.memoizedProps.id, phase, effectDuration, commitTime);
- } // Schedule a passive effect for this Profiler to call onPostCommit hooks.
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
- // because the effect is also where times bubble to parent Profilers.
-
-
- enqueuePendingPassiveProfilerEffect(finishedWork); // Propagate layout effect durations to the next nearest Profiler ancestor.
- // Do not reset these values until the next render so DevTools has a chance to read them first.
+ prepareToHydrateHostSuspenseInstance(workInProgress);
+ bubbleProperties(workInProgress);
- var parentFiber = finishedWork.return;
+ {
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ var isTimedOutSuspense = nextState !== null;
- outer: while (parentFiber !== null) {
- switch (parentFiber.tag) {
- case HostRoot:
- var root = parentFiber.stateNode;
- root.effectDuration += effectDuration;
- break outer;
+ if (isTimedOutSuspense) {
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
+ var primaryChildFragment = workInProgress.child;
- case Profiler:
- var parentStateNode = parentFiber.stateNode;
- parentStateNode.effectDuration += effectDuration;
- break outer;
+ if (primaryChildFragment !== null) {
+ // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
+ workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration;
+ }
}
-
- parentFiber = parentFiber.return;
}
}
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
-}
-function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
- // When updating this function, also update reappearLayoutEffects, which does
- // most of the same things when an offscreen tree goes from hidden -> visible.
- var flags = finishedWork.flags;
+ return false;
+ } else {
+ emitPendingHydrationWarnings(); // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
+ // state since we're now exiting out of it. popHydrationState doesn't do that for us.
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ resetHydrationState();
- if (flags & Update) {
- commitHookLayoutEffects(finishedWork, Layout | HasEffect);
- }
+ if ((workInProgress.flags & DidCapture) === NoFlags$1) {
+ // This boundary did not suspend so it's now hydrated and unsuspended.
+ workInProgress.memoizedState = null;
+ } // If nothing suspended, we need to schedule an effect to mark this boundary
+ // as having hydrated so events know that they're free to be invoked.
+ // It's also a signal to replay events and the suspense callback.
+ // If something suspended, schedule an effect to attach retry listeners.
+ // So we might as well always mark this.
- break;
- }
- case ClassComponent:
+ workInProgress.flags |= Update;
+ bubbleProperties(workInProgress);
+
{
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ var _isTimedOutSuspense = nextState !== null;
- if (flags & Update) {
- commitClassLayoutLifecycles(finishedWork, current);
- }
+ if (_isTimedOutSuspense) {
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
+ var _primaryChildFragment = workInProgress.child;
- if (flags & Callback) {
- commitClassCallbacks(finishedWork);
+ if (_primaryChildFragment !== null) {
+ // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
+ workInProgress.treeBaseDuration -= _primaryChildFragment.treeBaseDuration;
+ }
+ }
}
+ }
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
- }
+ return false;
+ }
+ } else {
+ // Successfully completed this tree. If this was a forced client render,
+ // there may have been recoverable errors during first hydration
+ // attempt. If so, add them to a queue so we can log them in the
+ // commit phase.
+ upgradeHydrationErrorsToRecoverable(); // Fall through to normal Suspense path
- break;
- }
+ return true;
+ }
+}
- case HostRoot:
- {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+function completeWork(current, workInProgress, renderLanes) {
+ var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing
+ // to the current tree provider fiber is just as fast and less error-prone.
+ // Ideally we would have a special version of the work loop only
+ // for hydration.
- if (flags & Callback) {
- // TODO: I think this is now always non-null by the time it reaches the
- // commit phase. Consider removing the type check.
- var updateQueue = finishedWork.updateQueue;
+ popTreeContext(workInProgress);
- if (updateQueue !== null) {
- var instance = null;
+ switch (workInProgress.tag) {
+ case IncompleteFunctionComponent:
+ {
+ {
+ break;
+ } // Fallthrough
- if (finishedWork.child !== null) {
- switch (finishedWork.child.tag) {
- case HostSingleton:
- case HostComponent:
- instance = getPublicInstance(finishedWork.child.stateNode);
- break;
+ }
- case ClassComponent:
- instance = finishedWork.child.stateNode;
- break;
- }
- }
+ case LazyComponent:
+ case SimpleMemoComponent:
+ case FunctionComponent:
+ case ForwardRef:
+ case Fragment:
+ case Mode:
+ case Profiler:
+ case ContextConsumer:
+ case MemoComponent:
+ bubbleProperties(workInProgress);
+ return null;
- try {
- commitCallbacks(updateQueue, instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
+ case ClassComponent:
+ {
- break;
+ bubbleProperties(workInProgress);
+ return null;
}
- case HostHoistable:
+ case HostRoot:
{
+ var fiberRoot = workInProgress.stateNode;
+
{
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ var previousCache = null;
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
+ if (current !== null) {
+ previousCache = current.memoizedState.cache;
}
- break;
- } // Fall through
-
- }
+ var cache = workInProgress.memoizedState.cache;
- case HostSingleton:
- case HostComponent:
- {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork); // Renderers may schedule work to be done after host components are mounted
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
- // These effects should only be committed when components are first mounted,
- // aka when there is no current/alternate.
+ if (cache !== previousCache) {
+ // Run passive effects to retain/release the cache.
+ workInProgress.flags |= Passive$1;
+ }
- if (current === null && flags & Update) {
- commitHostComponentMount(finishedWork);
+ popCacheProvider(workInProgress);
}
+ popHostContainer(workInProgress);
- if (flags & Ref) {
- safelyAttachRef(finishedWork, finishedWork.return);
+ if (fiberRoot.pendingContext) {
+ fiberRoot.context = fiberRoot.pendingContext;
+ fiberRoot.pendingContext = null;
}
- break;
- }
+ if (current === null || current.child === null) {
+ // If we hydrated, pop so that we can delete any remaining children
+ // that weren't hydrated.
+ var wasHydrated = popHydrationState(workInProgress);
- case Profiler:
- {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork); // TODO: Should this fire inside an offscreen tree? Or should it wait to
- // fire when the tree becomes visible again.
+ if (wasHydrated) {
+ emitPendingHydrationWarnings(); // If we hydrated, then we'll need to schedule an update for
+ // the commit side-effects on the root.
- if (flags & Update) {
- commitProfilerUpdate(finishedWork, current);
+ markUpdate(workInProgress);
+ } else {
+ if (current !== null) {
+ var prevState = current.memoizedState;
+
+ if ( // Check if this is a client root
+ !prevState.isDehydrated || // Check if we reverted to client rendering (e.g. due to an error)
+ (workInProgress.flags & ForceClientRender) !== NoFlags$1) {
+ // Schedule an effect to clear this container at the start of the
+ // next commit. This handles the case of React rendering into a
+ // container with previous children. It's also safe to do for
+ // updates too, because current.child would only be null if the
+ // previous render was null (so the container would already
+ // be empty).
+ workInProgress.flags |= Snapshot; // If this was a forced client render, there may have been
+ // recoverable errors during first hydration attempt. If so, add
+ // them to a queue so we can log them in the commit phase.
+
+ upgradeHydrationErrorsToRecoverable();
+ }
+ }
+ }
}
+ bubbleProperties(workInProgress);
- break;
+ return null;
}
- case SuspenseComponent:
+ case HostHoistable:
{
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
+ {
+ var nextResource = workInProgress.memoizedState;
- if (flags & Update) {
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
- }
+ if (current === null) {
+ // We are mounting and must Update this Hoistable in this commit
+ // @TODO refactor this block to create the instance here in complete
+ // phase if we are not hydrating.
+ markUpdate(workInProgress);
- break;
- }
+ if (nextResource !== null) {
+ // This is a Hoistable Resource
+ // This must come at the very end of the complete phase.
+ bubbleProperties(workInProgress);
+ preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
+ return null;
+ } else {
+ // This is a Hoistable Instance
+ // This must come at the very end of the complete phase.
+ bubbleProperties(workInProgress);
+ preloadInstanceAndSuspendIfNeeded(workInProgress);
+ return null;
+ }
+ } else {
+ // We are updating.
+ var currentResource = current.memoizedState;
- case OffscreenComponent:
- {
- var isModernRoot = disableLegacyMode ;
+ if (nextResource !== currentResource) {
+ // We are transitioning to, from, or between Hoistable Resources
+ // and require an update
+ markUpdate(workInProgress);
+ }
- if (isModernRoot) {
- var isHidden = finishedWork.memoizedState !== null;
- var newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden;
+ if (nextResource !== null) {
+ // This is a Hoistable Resource
+ // This must come at the very end of the complete phase.
+ bubbleProperties(workInProgress);
- if (newOffscreenSubtreeIsHidden) ; else {
- // The Offscreen tree is visible.
- var wasHidden = current !== null && current.memoizedState !== null;
- var newOffscreenSubtreeWasHidden = wasHidden || offscreenSubtreeWasHidden;
- var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
- var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
- offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
+ if (nextResource === currentResource) {
+ workInProgress.flags &= ~MaySuspendCommit;
+ } else {
+ preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
+ }
- if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
- // This is the root of a reappearing boundary. As we continue
- // traversing the layout effects, we must also re-mount layout
- // effects that were unmounted when the Offscreen subtree was
- // hidden. So this is a superset of the normal commitLayoutEffects.
- var includeWorkInProgressEffects = (finishedWork.subtreeFlags & LayoutMask) !== NoFlags$1;
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
+ return null;
} else {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
- }
+ // This is a Hoistable Instance
+ // We may have props to update on the Hoistable instance.
+ {
+ var oldProps = current.memoizedProps;
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- }
- } else {
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
- }
+ if (oldProps !== newProps) {
+ markUpdate(workInProgress);
+ }
+ } // This must come at the very end of the complete phase.
- if (flags & Ref) {
- var props = finishedWork.memoizedProps;
- if (props.mode === 'manual') {
- safelyAttachRef(finishedWork, finishedWork.return);
- } else {
- safelyDetachRef(finishedWork, finishedWork.return);
+ bubbleProperties(workInProgress);
+ preloadInstanceAndSuspendIfNeeded(workInProgress);
+ return null;
+ }
}
- }
+ } // Fall through
- break;
}
- default:
+ case HostSingleton:
{
- recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
- break;
- }
- }
-}
-
-function hideOrUnhideAllChildren(finishedWork, isHidden) {
- // Only hide or unhide the top-most host nodes.
- var hostSubtreeRoot = null;
+ {
+ popHostContext(workInProgress);
+ var rootContainerInstance = getRootHostContainer();
+ var _type = workInProgress.type;
- {
- // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
- var node = finishedWork;
+ if (current !== null && workInProgress.stateNode != null) {
+ {
+ var _oldProps2 = current.memoizedProps;
- while (true) {
- if (node.tag === HostComponent || (node.tag === HostHoistable ) || (node.tag === HostSingleton )) {
- if (hostSubtreeRoot === null) {
- hostSubtreeRoot = node;
+ if (_oldProps2 !== newProps) {
+ markUpdate(workInProgress);
+ }
+ }
+ } else {
+ if (!newProps) {
+ if (workInProgress.stateNode === null) {
+ throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
+ } // This can happen when we abort work.
- try {
- var instance = node.stateNode;
- if (isHidden) {
- hideInstance(instance);
- } else {
- unhideInstance(node.stateNode, node.memoizedProps);
+ bubbleProperties(workInProgress);
+ return null;
}
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- } else if (node.tag === HostText) {
- if (hostSubtreeRoot === null) {
- try {
- var _instance = node.stateNode;
- if (isHidden) {
- hideTextInstance(_instance);
+ var currentHostContext = getHostContext();
+
+ var _wasHydrated = popHydrationState(workInProgress);
+
+ var instance;
+
+ if (_wasHydrated) {
+ // We ignore the boolean indicating there is an updateQueue because
+ // it is used only to set text children and HostSingletons do not
+ // use them.
+ prepareToHydrateHostInstance(workInProgress, currentHostContext);
+ instance = workInProgress.stateNode;
} else {
- unhideTextInstance(_instance, node.memoizedProps);
+ instance = resolveSingletonInstance(_type, newProps, rootContainerInstance, currentHostContext, true);
+ workInProgress.stateNode = instance;
+ markUpdate(workInProgress);
}
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
- }
- } else if ((node.tag === OffscreenComponent || node.tag === LegacyHiddenComponent) && node.memoizedState !== null && node !== finishedWork) ; else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
-
- if (node === finishedWork) {
- return;
- }
- while (node.sibling === null) {
- if (node.return === null || node.return === finishedWork) {
- return;
- }
-
- if (hostSubtreeRoot === node) {
- hostSubtreeRoot = null;
- }
+ bubbleProperties(workInProgress);
+ return null;
+ } // Fall through
- node = node.return;
}
- if (hostSubtreeRoot === node) {
- hostSubtreeRoot = null;
- }
+ case HostComponent:
+ {
+ popHostContext(workInProgress);
+ var _type2 = workInProgress.type;
- node.sibling.return = node.return;
- node = node.sibling;
- }
- }
-}
+ if (current !== null && workInProgress.stateNode != null) {
+ updateHostComponent(current, workInProgress, _type2, newProps);
+ } else {
+ if (!newProps) {
+ if (workInProgress.stateNode === null) {
+ throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
+ } // This can happen when we abort work.
-function commitAttachRef(finishedWork) {
- var ref = finishedWork.ref;
- if (ref !== null) {
- var instance = finishedWork.stateNode;
- var instanceToUse;
+ bubbleProperties(workInProgress);
+ return null;
+ }
- switch (finishedWork.tag) {
- case HostHoistable:
- case HostSingleton:
- case HostComponent:
- instanceToUse = getPublicInstance(instance);
- break;
+ var _currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
+ // "stack" as the parent. Then append children as we go in beginWork
+ // or completeWork depending on whether we want to add them top->down or
+ // bottom->up. Top->down is faster in IE11.
- default:
- instanceToUse = instance;
- } // Moved outside to ensure DCE works with this flag
- if (typeof ref === 'function') {
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- finishedWork.refCleanup = ref(instanceToUse);
- } finally {
- recordLayoutEffectDuration(finishedWork);
- }
- } else {
- finishedWork.refCleanup = ref(instanceToUse);
- }
- } else {
- {
- // TODO: We should move these warnings to happen during the render
- // phase (markRef).
- if (typeof ref === 'string') {
- error('String refs are no longer supported.');
- } else if (!ref.hasOwnProperty('current')) {
- error('Unexpected ref object provided for %s. ' + 'Use either a ref-setter function or React.createRef().', getComponentNameFromFiber(finishedWork));
- }
- } // $FlowFixMe[incompatible-use] unable to narrow type to the non-function case
+ var _wasHydrated2 = popHydrationState(workInProgress);
+ if (_wasHydrated2) {
+ // TODO: Move this and createInstance step into the beginPhase
+ // to consolidate.
+ prepareToHydrateHostInstance(workInProgress, _currentHostContext);
+ } else {
+ var _rootContainerInstance = getRootHostContainer();
- ref.current = instanceToUse;
- }
- }
-}
+ var _instance3 = createInstance(_type2, newProps, _rootContainerInstance, _currentHostContext, workInProgress); // TODO: For persistent renderers, we should pass children as part
+ // of the initial instance creation
-function detachFiberMutation(fiber) {
- // Cut off the return pointer to disconnect it from the tree.
- // This enables us to detect and warn against state updates on an unmounted component.
- // It also prevents events from bubbling from within disconnected components.
- //
- // Ideally, we should also clear the child pointer of the parent alternate to let this
- // get GC:ed but we don't know which for sure which parent is the current
- // one so we'll settle for GC:ing the subtree of this child.
- // This child itself will be GC:ed when the parent updates the next time.
- //
- // Note that we can't clear child or sibling pointers yet.
- // They're needed for passive effects and for findDOMNode.
- // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
- //
- // Don't reset the alternate yet, either. We need that so we can detach the
- // alternate's fields in the passive phase. Clearing the return pointer is
- // sufficient for findDOMNode semantics.
- var alternate = fiber.alternate;
- if (alternate !== null) {
- alternate.return = null;
- }
+ appendAllChildren(_instance3, workInProgress);
+ workInProgress.stateNode = _instance3; // Certain renderers require commit-time effects for initial mount.
+ // (eg DOM renderer supports auto-focus for certain elements).
+ // Make sure such renderers get scheduled for later work.
- fiber.return = null;
-}
+ if (finalizeInitialChildren(_instance3, _type2, newProps)) {
+ markUpdate(workInProgress);
+ }
+ }
+ }
-function detachFiberAfterEffects(fiber) {
- var alternate = fiber.alternate;
+ bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
+ // throw to suspend, and if the resource immediately loads, the work loop
+ // will resume rendering as if the work-in-progress completed. So it must
+ // fully complete.
- if (alternate !== null) {
- fiber.alternate = null;
- detachFiberAfterEffects(alternate);
- } // Clear cyclical Fiber fields. This level alone is designed to roughly
- // approximate the planned Fiber refactor. In that world, `setState` will be
- // bound to a special "instance" object instead of a Fiber. The Instance
- // object will not have any of these fields. It will only be connected to
- // the fiber tree via a single link at the root. So if this level alone is
- // sufficient to fix memory issues, that bodes well for our plans.
+ preloadInstanceAndSuspendIfNeeded(workInProgress);
+ return null;
+ }
+ case HostText:
+ {
+ var newText = newProps;
- fiber.child = null;
- fiber.deletions = null;
- fiber.sibling = null; // The `stateNode` is cyclical because on host nodes it points to the host
- // tree, which has its own pointers to children, parents, and siblings.
- // The other host nodes also point back to fibers, so we should detach that
- // one, too.
+ if (current && workInProgress.stateNode != null) {
+ var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
+ // to schedule a side-effect to do the updates.
- if (fiber.tag === HostComponent) {
- var hostInstance = fiber.stateNode;
+ updateHostText(current, workInProgress, oldText, newText);
+ } else {
+ if (typeof newText !== 'string') {
+ if (workInProgress.stateNode === null) {
+ throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
+ } // This can happen when we abort work.
- if (hostInstance !== null) {
- detachDeletedInstance(hostInstance);
- }
- }
+ }
- fiber.stateNode = null;
+ var _rootContainerInstance2 = getRootHostContainer();
- {
- fiber._debugOwner = null;
- } // Theoretically, nothing in here should be necessary, because we already
- // disconnected the fiber from the tree. So even if something leaks this
- // particular fiber, it won't leak anything else.
+ var _currentHostContext2 = getHostContext();
+ var _wasHydrated3 = popHydrationState(workInProgress);
- fiber.return = null;
- fiber.dependencies = null;
- fiber.memoizedProps = null;
- fiber.memoizedState = null;
- fiber.pendingProps = null;
- fiber.stateNode = null; // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
+ if (_wasHydrated3) {
+ prepareToHydrateHostTextInstance(workInProgress);
+ } else {
+ workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance2, _currentHostContext2, workInProgress);
+ }
+ }
- fiber.updateQueue = null;
-}
+ bubbleProperties(workInProgress);
+ return null;
+ }
-function getHostParentFiber(fiber) {
- var parent = fiber.return;
+ case SuspenseComponent:
+ {
+ var nextState = workInProgress.memoizedState; // Special path for dehydrated boundaries. We may eventually move this
+ // to its own fiber type so that we can add other kinds of hydration
+ // boundaries that aren't associated with a Suspense tree. In anticipation
+ // of such a refactor, all the hydration logic is contained in
+ // this branch.
- while (parent !== null) {
- if (isHostParent(parent)) {
- return parent;
- }
+ if (current === null || current.memoizedState !== null && current.memoizedState.dehydrated !== null) {
+ var fallthroughToNormalSuspensePath = completeDehydratedSuspenseBoundary(current, workInProgress, nextState);
- parent = parent.return;
- }
+ if (!fallthroughToNormalSuspensePath) {
+ if (workInProgress.flags & ForceClientRender) {
+ popSuspenseHandler(workInProgress); // Special case. There were remaining unhydrated nodes. We treat
+ // this as a mismatch. Revert to client rendering.
- throw new Error('Expected to find a host parent. This error is likely caused by a bug ' + 'in React. Please file an issue.');
-}
+ return workInProgress;
+ } else {
+ popSuspenseHandler(workInProgress); // Did not finish hydrating, either because this is the initial
+ // render or because something suspended.
-function isHostParent(fiber) {
- return fiber.tag === HostComponent || fiber.tag === HostRoot || (fiber.tag === HostHoistable ) || (fiber.tag === HostSingleton ) || fiber.tag === HostPortal;
-}
+ return null;
+ }
+ } // Continue with the normal Suspense path.
-function getHostSibling(fiber) {
- // We're going to search forward into the tree until we find a sibling host
- // node. Unfortunately, if multiple insertions are done in a row we have to
- // search past them. This leads to exponential search for the next sibling.
- // TODO: Find a more efficient way to do this.
- var node = fiber;
+ }
- siblings: while (true) {
- // If we didn't find anything, let's try the next sibling.
- while (node.sibling === null) {
- if (node.return === null || isHostParent(node.return)) {
- // If we pop out of the root or hit the parent the fiber we are the
- // last sibling.
- return null;
- } // $FlowFixMe[incompatible-type] found when upgrading Flow
+ popSuspenseHandler(workInProgress);
+ if ((workInProgress.flags & DidCapture) !== NoFlags$1) {
+ // Something suspended. Re-render with the fallback children.
+ workInProgress.lanes = renderLanes; // Do not reset the effect list.
- node = node.return;
- }
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ transferActualDuration(workInProgress);
+ } // Don't bubble properties in this case.
- node.sibling.return = node.return;
- node = node.sibling;
- while (node.tag !== HostComponent && node.tag !== HostText && (node.tag !== HostSingleton) && node.tag !== DehydratedFragment) {
- // If it is not host node and, we might have a host node inside it.
- // Try to search down until we find one.
- if (node.flags & Placement) {
- // If we don't have a child, try the siblings instead.
- continue siblings;
- } // If we don't have a child, try the siblings instead.
- // We also skip portals because they are not part of this host tree.
+ return workInProgress;
+ }
+ var nextDidTimeout = nextState !== null;
+ var prevDidTimeout = current !== null && current.memoizedState !== null;
- if (node.child === null || node.tag === HostPortal) {
- continue siblings;
- } else {
- node.child.return = node;
- node = node.child;
- }
- } // Check if this host node is stable or about to be placed.
+ if (nextDidTimeout) {
+ var offscreenFiber = workInProgress.child;
+ var _previousCache = null;
+ if (offscreenFiber.alternate !== null && offscreenFiber.alternate.memoizedState !== null && offscreenFiber.alternate.memoizedState.cachePool !== null) {
+ _previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;
+ }
- if (!(node.flags & Placement)) {
- // Found it!
- return node.stateNode;
- }
- }
-}
+ var _cache = null;
-function commitPlacement(finishedWork) {
+ if (offscreenFiber.memoizedState !== null && offscreenFiber.memoizedState.cachePool !== null) {
+ _cache = offscreenFiber.memoizedState.cachePool.pool;
+ }
- {
- if (finishedWork.tag === HostSingleton) {
- // Singletons are already in the Host and don't need to be placed
- // Since they operate somewhat like Portals though their children will
- // have Placement and will get placed inside them
- return;
- }
- } // Recursively insert all host nodes into the parent.
+ if (_cache !== _previousCache) {
+ // Run passive effects to retain/release the cache.
+ offscreenFiber.flags |= Passive$1;
+ }
+ } // If the suspended state of the boundary changes, we need to schedule
+ // a passive effect, which is when we process the transitions
- var parentFiber = getHostParentFiber(finishedWork);
+ if (nextDidTimeout !== prevDidTimeout) {
+ // an effect to toggle the subtree's visibility. When we switch from
+ // fallback -> primary, the inner Offscreen fiber schedules this effect
+ // as part of its normal complete phase. But when we switch from
+ // primary -> fallback, the inner Offscreen fiber does not have a complete
+ // phase. So we need to schedule its effect here.
+ //
+ // We also use this flag to connect/disconnect the effects, but the same
+ // logic applies: when re-connecting, the Offscreen fiber's complete
+ // phase will handle scheduling the effect. It's only when the fallback
+ // is active that we have to do anything special.
- switch (parentFiber.tag) {
- case HostSingleton:
- {
- {
- var parent = parentFiber.stateNode;
- var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
- insertOrAppendPlacementNode(finishedWork, before, parent);
- break;
- } // Fall through
+ if (nextDidTimeout) {
+ var _offscreenFiber2 = workInProgress.child;
+ _offscreenFiber2.flags |= Visibility;
+ }
+ }
- }
+ var retryQueue = workInProgress.updateQueue;
+ scheduleRetryEffect(workInProgress, retryQueue);
- case HostComponent:
- {
- var _parent = parentFiber.stateNode;
+ bubbleProperties(workInProgress);
- if (parentFiber.flags & ContentReset) {
- // Reset the text content of the parent before doing any insertions
- resetTextContent(_parent); // Clear ContentReset from the effect tag
+ {
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ if (nextDidTimeout) {
+ // Don't count time spent in a timed out Suspense subtree as part of the base duration.
+ var primaryChildFragment = workInProgress.child;
- parentFiber.flags &= ~ContentReset;
+ if (primaryChildFragment !== null) {
+ // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
+ workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration;
+ }
+ }
+ }
}
- var _before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its
- // children to find all the terminal nodes.
-
-
- insertOrAppendPlacementNode(finishedWork, _before, _parent);
- break;
+ return null;
}
- case HostRoot:
case HostPortal:
- {
- var _parent2 = parentFiber.stateNode.containerInfo;
-
- var _before2 = getHostSibling(finishedWork);
+ popHostContainer(workInProgress);
- insertOrAppendPlacementNodeIntoContainer(finishedWork, _before2, _parent2);
- break;
+ if (current === null) {
+ preparePortalMount(workInProgress.stateNode.containerInfo);
}
- default:
- throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.');
- }
-}
-
-function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
- var tag = node.tag;
- var isHost = tag === HostComponent || tag === HostText;
+ bubbleProperties(workInProgress);
+ return null;
- if (isHost) {
- var stateNode = node.stateNode;
+ case ContextProvider:
+ // Pop provider fiber
+ var context;
- if (before) {
- insertInContainerBefore(parent, stateNode, before);
- } else {
- appendChildToContainer(parent, stateNode);
- }
- } else if (tag === HostPortal || (tag === HostSingleton )) ; else {
- var child = node.child;
+ {
+ context = workInProgress.type;
+ }
- if (child !== null) {
- insertOrAppendPlacementNodeIntoContainer(child, before, parent);
- var sibling = child.sibling;
+ popProvider(context, workInProgress);
+ bubbleProperties(workInProgress);
+ return null;
- while (sibling !== null) {
- insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
- sibling = sibling.sibling;
+ case IncompleteClassComponent:
+ {
+ {
+ break;
+ } // Same as class component case. I put it down here so that the tags are
}
- }
- }
-}
-function insertOrAppendPlacementNode(node, before, parent) {
- var tag = node.tag;
- var isHost = tag === HostComponent || tag === HostText;
+ case SuspenseListComponent:
+ {
+ popSuspenseListContext(workInProgress);
+ var renderState = workInProgress.memoizedState;
- if (isHost) {
- var stateNode = node.stateNode;
+ if (renderState === null) {
+ // We're running in the default, "independent" mode.
+ // We don't do anything in this mode.
+ bubbleProperties(workInProgress);
+ return null;
+ }
- if (before) {
- insertBefore(parent, stateNode, before);
- } else {
- appendChild(parent, stateNode);
- }
- } else if (tag === HostPortal || (tag === HostSingleton )) ; else {
- var child = node.child;
+ var didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags$1;
+ var renderedTail = renderState.rendering;
- if (child !== null) {
- insertOrAppendPlacementNode(child, before, parent);
- var sibling = child.sibling;
+ if (renderedTail === null) {
+ // We just rendered the head.
+ if (!didSuspendAlready) {
+ // This is the first pass. We need to figure out if anything is still
+ // suspended in the rendered set.
+ // If new content unsuspended, but there's still some content that
+ // didn't. Then we need to do a second pass that forces everything
+ // to keep showing their fallbacks.
+ // We might be suspended if something in this render pass suspended, or
+ // something in the previous committed pass suspended. Otherwise,
+ // there's no chance so we can skip the expensive call to
+ // findFirstSuspended.
+ var cannotBeSuspended = renderHasNotSuspendedYet() && (current === null || (current.flags & DidCapture) === NoFlags$1);
- while (sibling !== null) {
- insertOrAppendPlacementNode(sibling, before, parent);
- sibling = sibling.sibling;
- }
- }
- }
-} // These are tracked on the stack as we recursively traverse a
-// deleted subtree.
-// TODO: Update these during the whole mutation phase, not just during
-// a deletion.
+ if (!cannotBeSuspended) {
+ var row = workInProgress.child;
+ while (row !== null) {
+ var suspended = findFirstSuspended(row);
-var hostParent = null;
-var hostParentIsContainer = false;
+ if (suspended !== null) {
+ didSuspendAlready = true;
+ workInProgress.flags |= DidCapture;
+ cutOffTailIfNeeded(renderState, false); // If this is a newly suspended tree, it might not get committed as
+ // part of the second pass. In that case nothing will subscribe to
+ // its thenables. Instead, we'll transfer its thenables to the
+ // SuspenseList so that it can retry if they resolve.
+ // There might be multiple of these in the list but since we're
+ // going to wait for all of them anyway, it doesn't really matter
+ // which ones gets to ping. In theory we could get clever and keep
+ // track of how many dependencies remain but it gets tricky because
+ // in the meantime, we can add/remove/change items and dependencies.
+ // We might bail out of the loop before finding any but that
+ // doesn't matter since that means that the other boundaries that
+ // we did find already has their listeners attached.
-function commitDeletionEffects(root, returnFiber, deletedFiber) {
- {
- // We only have the top Fiber that was deleted but we need to recurse down its
- // children to find all the terminal nodes.
- // Recursively delete all host nodes from the parent, detach refs, clean
- // up mounted layout effects, and call componentWillUnmount.
- // We only need to remove the topmost host child in each branch. But then we
- // still need to keep traversing to unmount effects, refs, and cWU. TODO: We
- // could split this into two separate traversals functions, where the second
- // one doesn't include any removeChild logic. This is maybe the same
- // function as "disappearLayoutEffects" (or whatever that turns into after
- // the layout phase is refactored to use recursion).
- // Before starting, find the nearest host parent on the stack so we know
- // which instance/container to remove the children from.
- // TODO: Instead of searching up the fiber return path on every deletion, we
- // can track the nearest host component on the JS stack as we traverse the
- // tree during the commit phase. This would make insertions faster, too.
- var parent = returnFiber;
+ var _retryQueue = suspended.updateQueue;
+ workInProgress.updateQueue = _retryQueue;
+ scheduleRetryEffect(workInProgress, _retryQueue); // Rerender the whole list, but this time, we'll force fallbacks
+ // to stay in place.
+ // Reset the effect flags before doing the second pass since that's now invalid.
+ // Reset the child fibers to their original state.
- findParent: while (parent !== null) {
- switch (parent.tag) {
- case HostSingleton:
- case HostComponent:
- {
- hostParent = parent.stateNode;
- hostParentIsContainer = false;
- break findParent;
- }
+ workInProgress.subtreeFlags = NoFlags$1;
+ resetChildFibers(workInProgress, renderLanes); // Set up the Suspense List Context to force suspense and
+ // immediately rerender the children.
- case HostRoot:
- {
- hostParent = parent.stateNode.containerInfo;
- hostParentIsContainer = true;
- break findParent;
- }
+ pushSuspenseListContext(workInProgress, setShallowSuspenseListContext(suspenseStackCursor.current, ForceSuspenseFallback)); // Don't bubble properties in this case.
- case HostPortal:
- {
- hostParent = parent.stateNode.containerInfo;
- hostParentIsContainer = true;
- break findParent;
- }
- }
+ return workInProgress.child;
+ }
- parent = parent.return;
- }
+ row = row.sibling;
+ }
+ }
- if (hostParent === null) {
- throw new Error('Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.');
- }
+ if (renderState.tail !== null && now$1() > getRenderTargetTime()) {
+ // We have already passed our CPU deadline but we still have rows
+ // left in the tail. We'll just give up further attempts to render
+ // the main content and only render fallbacks.
+ workInProgress.flags |= DidCapture;
+ didSuspendAlready = true;
+ cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
+ // to get it started back up to attempt the next item. While in terms
+ // of priority this work has the same priority as this current render,
+ // it's not part of the same transition once the transition has
+ // committed. If it's sync, we still want to yield so that it can be
+ // painted. Conceptually, this is really the same as pinging.
+ // We can use any RetryLane even if it's the one currently rendering
+ // since we're leaving it behind on this node.
- commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
- hostParent = null;
- hostParentIsContainer = false;
- }
+ workInProgress.lanes = SomeRetryLane;
+ }
+ } else {
+ cutOffTailIfNeeded(renderState, false);
+ } // Next we're going to render the tail.
- detachFiberMutation(deletedFiber);
-}
+ } else {
+ // Append the rendered row to the child list.
+ if (!didSuspendAlready) {
+ var _suspended = findFirstSuspended(renderedTail);
-function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) {
- // TODO: Use a static flag to skip trees that don't have unmount effects
- var child = parent.child;
+ if (_suspended !== null) {
+ workInProgress.flags |= DidCapture;
+ didSuspendAlready = true; // Ensure we transfer the update queue to the parent so that it doesn't
+ // get lost if this row ends up dropped during a second pass.
- while (child !== null) {
- commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
- child = child.sibling;
- }
-}
+ var _retryQueue2 = _suspended.updateQueue;
+ workInProgress.updateQueue = _retryQueue2;
+ scheduleRetryEffect(workInProgress, _retryQueue2);
+ cutOffTailIfNeeded(renderState, true); // This might have been modified.
-function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) {
- onCommitUnmount(deletedFiber); // The cases in this outer switch modify the stack before they traverse
- // into their subtree. There are simpler cases in the inner switch
- // that don't modify the stack.
+ if (renderState.tail === null && renderState.tailMode === 'hidden' && !renderedTail.alternate && !getIsHydrating() // We don't cut it if we're hydrating.
+ ) {
+ // We're done.
+ bubbleProperties(workInProgress);
+ return null;
+ }
+ } else if ( // The time it took to render last row is greater than the remaining
+ // time we have to render. So rendering one more row would likely
+ // exceed it.
+ now$1() * 2 - renderState.renderingStartTime > getRenderTargetTime() && renderLanes !== OffscreenLane) {
+ // We have now passed our CPU deadline and we'll just give up further
+ // attempts to render the main content and only render fallbacks.
+ // The assumption is that this is usually faster.
+ workInProgress.flags |= DidCapture;
+ didSuspendAlready = true;
+ cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
+ // to get it started back up to attempt the next item. While in terms
+ // of priority this work has the same priority as this current render,
+ // it's not part of the same transition once the transition has
+ // committed. If it's sync, we still want to yield so that it can be
+ // painted. Conceptually, this is really the same as pinging.
+ // We can use any RetryLane even if it's the one currently rendering
+ // since we're leaving it behind on this node.
- switch (deletedFiber.tag) {
- case HostHoistable:
- {
- {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ workInProgress.lanes = SomeRetryLane;
+ }
}
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
+ if (renderState.isBackwards) {
+ // The effect list of the backwards tail will have been added
+ // to the end. This breaks the guarantee that life-cycles fire in
+ // sibling order but that isn't a strong guarantee promised by React.
+ // Especially since these might also just pop in during future commits.
+ // Append to the beginning of the list.
+ renderedTail.sibling = workInProgress.child;
+ workInProgress.child = renderedTail;
+ } else {
+ var previousSibling = renderState.last;
- if (deletedFiber.memoizedState) {
- releaseResource(deletedFiber.memoizedState);
- } else if (deletedFiber.stateNode) {
- unmountHoistable(deletedFiber.stateNode);
+ if (previousSibling !== null) {
+ previousSibling.sibling = renderedTail;
+ } else {
+ workInProgress.child = renderedTail;
+ }
+
+ renderState.last = renderedTail;
}
+ }
- return;
- } // Fall through
+ if (renderState.tail !== null) {
+ // We still have tail rows to render.
+ // Pop a row.
+ var next = renderState.tail;
+ renderState.rendering = next;
+ renderState.tail = next.sibling;
+ renderState.renderingStartTime = now$1();
+ next.sibling = null; // Restore the context.
+ // TODO: We can probably just avoid popping it instead and only
+ // setting it the first time we go from not suspended to suspended.
- }
+ var suspenseContext = suspenseStackCursor.current;
- case HostSingleton:
- {
- {
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
+ if (didSuspendAlready) {
+ suspenseContext = setShallowSuspenseListContext(suspenseContext, ForceSuspenseFallback);
+ } else {
+ suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
}
- var prevHostParent = hostParent;
- var prevHostParentIsContainer = hostParentIsContainer;
- hostParent = deletedFiber.stateNode;
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); // Normally this is called in passive unmount effect phase however with
- // HostSingleton we warn if you acquire one that is already associated to
- // a different fiber. To increase our chances of avoiding this, specifically
- // if you keyed a HostSingleton so there will be a delete followed by a Placement
- // we treat detach eagerly here
+ pushSuspenseListContext(workInProgress, suspenseContext); // Do a pass over the next row.
+ // Don't bubble properties in this case.
- releaseSingletonInstance(deletedFiber.stateNode);
- hostParent = prevHostParent;
- hostParentIsContainer = prevHostParentIsContainer;
- return;
- } // Fall through
+ return next;
+ }
+ bubbleProperties(workInProgress);
+ return null;
}
- case HostComponent:
+ case ScopeComponent:
{
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- } // Intentional fallthrough to next branch
+ break;
}
- case HostText:
+ case OffscreenComponent:
+ case LegacyHiddenComponent:
{
- // We only need to remove the nearest host child. Set the host parent
- // to `null` on the stack to indicate that nested children don't
- // need to be removed.
+ popSuspenseHandler(workInProgress);
+ popHiddenContext(workInProgress);
+ var _nextState = workInProgress.memoizedState;
+ var nextIsHidden = _nextState !== null; // Schedule a Visibility effect if the visibility has changed
+
{
- var _prevHostParent = hostParent;
- var _prevHostParentIsContainer = hostParentIsContainer;
- hostParent = null;
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- hostParent = _prevHostParent;
- hostParentIsContainer = _prevHostParentIsContainer;
+ if (current !== null) {
+ var _prevState = current.memoizedState;
+ var prevIsHidden = _prevState !== null;
- if (hostParent !== null) {
- // Now that all the child effects have unmounted, we can remove the
- // node from the tree.
- if (hostParentIsContainer) {
- removeChildFromContainer(hostParent, deletedFiber.stateNode);
- } else {
- removeChild(hostParent, deletedFiber.stateNode);
+ if (prevIsHidden !== nextIsHidden) {
+ workInProgress.flags |= Visibility;
+ }
+ } else {
+ // On initial mount, we only need a Visibility effect if the tree
+ // is hidden.
+ if (nextIsHidden) {
+ workInProgress.flags |= Visibility;
}
}
}
- return;
- }
+ if (!nextIsHidden || !disableLegacyMode ) {
+ bubbleProperties(workInProgress);
+ } else {
+ // Don't bubble properties for hidden children unless we're rendering
+ // at offscreen priority.
+ if (includesSomeLane(renderLanes, OffscreenLane) && // Also don't bubble if the tree suspended
+ (workInProgress.flags & DidCapture) === NoLanes) {
+ bubbleProperties(workInProgress); // Check if there was an insertion or update in the hidden subtree.
+ // If so, we need to hide those nodes in the commit phase, so
+ // schedule a visibility effect.
- case DehydratedFragment:
- {
- // Delete the dehydrated suspense boundary and all of its content.
+ if (workInProgress.subtreeFlags & (Placement | Update)) {
+ workInProgress.flags |= Visibility;
+ }
+ }
+ }
+
+ var offscreenQueue = workInProgress.updateQueue;
+ if (offscreenQueue !== null) {
+ var _retryQueue3 = offscreenQueue.retryQueue;
+ scheduleRetryEffect(workInProgress, _retryQueue3);
+ }
{
- if (hostParent !== null) {
- if (hostParentIsContainer) {
- clearSuspenseBoundaryFromContainer(hostParent, deletedFiber.stateNode);
- } else {
- clearSuspenseBoundary(hostParent, deletedFiber.stateNode);
- }
+ var _previousCache2 = null;
+
+ if (current !== null && current.memoizedState !== null && current.memoizedState.cachePool !== null) {
+ _previousCache2 = current.memoizedState.cachePool.pool;
+ }
+
+ var _cache2 = null;
+
+ if (workInProgress.memoizedState !== null && workInProgress.memoizedState.cachePool !== null) {
+ _cache2 = workInProgress.memoizedState.cachePool.pool;
+ }
+
+ if (_cache2 !== _previousCache2) {
+ // Run passive effects to retain/release the cache.
+ workInProgress.flags |= Passive$1;
}
}
- return;
+ popTransition(workInProgress, current);
+ return null;
}
- case HostPortal:
+ case CacheComponent:
{
{
- // When we go into a portal, it becomes the parent to remove from.
- var _prevHostParent2 = hostParent;
- var _prevHostParentIsContainer2 = hostParentIsContainer;
- hostParent = deletedFiber.stateNode.containerInfo;
- hostParentIsContainer = true;
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- hostParent = _prevHostParent2;
- hostParentIsContainer = _prevHostParentIsContainer2;
+ var _previousCache3 = null;
+
+ if (current !== null) {
+ _previousCache3 = current.memoizedState.cache;
+ }
+
+ var _cache3 = workInProgress.memoizedState.cache;
+
+ if (_cache3 !== _previousCache3) {
+ // Run passive effects to retain/release the cache.
+ workInProgress.flags |= Passive$1;
+ }
+
+ popCacheProvider(workInProgress);
+ bubbleProperties(workInProgress);
}
- return;
+ return null;
}
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent:
+ case TracingMarkerComponent:
{
- if (!offscreenSubtreeWasHidden) {
- var updateQueue = deletedFiber.updateQueue;
- if (updateQueue !== null) {
- var lastEffect = updateQueue.lastEffect;
+ return null;
+ }
+ }
- if (lastEffect !== null) {
- var firstEffect = lastEffect.next;
- var effect = firstEffect;
+ throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
+}
- do {
- var tag = effect.tag;
- var inst = effect.inst;
- var destroy = inst.destroy;
+function unwindWork(current, workInProgress, renderLanes) {
+ // Note: This intentionally doesn't check if we're hydrating because comparing
+ // to the current tree provider fiber is just as fast and less error-prone.
+ // Ideally we would have a special version of the work loop only
+ // for hydration.
+ popTreeContext(workInProgress);
- if (destroy !== undefined) {
- if ((tag & Insertion) !== NoFlags) {
- inst.destroy = undefined;
- safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
- } else if ((tag & Layout) !== NoFlags) {
- {
- markComponentLayoutEffectUnmountStarted(deletedFiber);
- }
+ switch (workInProgress.tag) {
+ case ClassComponent:
+ {
- if (shouldProfile(deletedFiber)) {
- startLayoutEffectTimer();
- inst.destroy = undefined;
- safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
- recordLayoutEffectDuration(deletedFiber);
- } else {
- inst.destroy = undefined;
- safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy);
- }
+ var flags = workInProgress.flags;
- {
- markComponentLayoutEffectUnmountStopped();
- }
- }
- }
+ if (flags & ShouldCapture) {
+ workInProgress.flags = flags & ~ShouldCapture | DidCapture;
- effect = effect.next;
- } while (effect !== firstEffect);
- }
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ transferActualDuration(workInProgress);
}
+
+ return workInProgress;
}
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- return;
+ return null;
}
- case ClassComponent:
+ case HostRoot:
{
- if (!offscreenSubtreeWasHidden) {
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
- var instance = deletedFiber.stateNode;
- if (typeof instance.componentWillUnmount === 'function') {
- safelyCallComponentWillUnmount(deletedFiber, nearestMountedAncestor, instance);
- }
+ {
+ popCacheProvider(workInProgress);
}
+ popHostContainer(workInProgress);
+ var _flags = workInProgress.flags;
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- return;
- }
+ if ((_flags & ShouldCapture) !== NoFlags$1 && (_flags & DidCapture) === NoFlags$1) {
+ // There was an error during render that wasn't captured by a suspense
+ // boundary. Do a second pass on the root to unmount the children.
+ workInProgress.flags = _flags & ~ShouldCapture | DidCapture;
+ return workInProgress;
+ } // We unwound to the root without completing it. Exit.
- case ScopeComponent:
- {
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- return;
+ return null;
}
- case OffscreenComponent:
+ case HostHoistable:
+ case HostSingleton:
+ case HostComponent:
{
- safelyDetachRef(deletedFiber, nearestMountedAncestor);
-
- {
- // If this offscreen component is hidden, we already unmounted it. Before
- // deleting the children, track that it's already unmounted so that we
- // don't attempt to unmount the effects again.
- // TODO: If the tree is hidden, in most cases we should be able to skip
- // over the nested children entirely. An exception is we haven't yet found
- // the topmost host node to delete, which we already track on the stack.
- // But the other case is portals, which need to be detached no matter how
- // deeply they are nested. We should use a subtree flag to track whether a
- // subtree includes a nested portal.
- var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- }
-
- break;
+ // TODO: popHydrationState
+ popHostContext(workInProgress);
+ return null;
}
- default:
+ case SuspenseComponent:
{
- recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber);
- return;
- }
- }
-}
-
-function commitSuspenseCallback(finishedWork) {
-}
-
-function commitSuspenseHydrationCallbacks(finishedRoot, finishedWork) {
-
- var newState = finishedWork.memoizedState;
+ popSuspenseHandler(workInProgress);
+ var suspenseState = workInProgress.memoizedState;
- if (newState === null) {
- var current = finishedWork.alternate;
+ if (suspenseState !== null && suspenseState.dehydrated !== null) {
+ if (workInProgress.alternate === null) {
+ throw new Error('Threw in newly mounted dehydrated component. This is likely a bug in ' + 'React. Please file an issue.');
+ }
- if (current !== null) {
- var prevState = current.memoizedState;
+ resetHydrationState();
+ }
- if (prevState !== null) {
- var suspenseInstance = prevState.dehydrated;
+ var _flags2 = workInProgress.flags;
- if (suspenseInstance !== null) {
- try {
- commitHydratedSuspenseInstance(suspenseInstance);
+ if (_flags2 & ShouldCapture) {
+ workInProgress.flags = _flags2 & ~ShouldCapture | DidCapture; // Captured a suspense effect. Re-render the boundary.
- var hydrationCallbacks, onHydrated; if (enableSuspenseCallback) ;
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ transferActualDuration(workInProgress);
}
+
+ return workInProgress;
}
+
+ return null;
}
- }
- }
-}
-function getRetryCache(finishedWork) {
- // TODO: Unify the interface for the retry cache so we don't have to switch
- // on the tag like this.
- switch (finishedWork.tag) {
- case SuspenseComponent:
case SuspenseListComponent:
{
- var retryCache = finishedWork.stateNode;
+ popSuspenseListContext(workInProgress); // SuspenseList doesn't actually catch anything. It should've been
+ // caught by a nested boundary. If not, it should bubble through.
+
+ return null;
+ }
+
+ case HostPortal:
+ popHostContainer(workInProgress);
+ return null;
- if (retryCache === null) {
- retryCache = finishedWork.stateNode = new PossiblyWeakSet();
- }
+ case ContextProvider:
+ var context;
- return retryCache;
+ {
+ context = workInProgress.type;
}
+ popProvider(context, workInProgress);
+ return null;
+
case OffscreenComponent:
+ case LegacyHiddenComponent:
{
- var instance = finishedWork.stateNode;
- var _retryCache = instance._retryCache;
+ popSuspenseHandler(workInProgress);
+ popHiddenContext(workInProgress);
+ popTransition(workInProgress, current);
+ var _flags3 = workInProgress.flags;
- if (_retryCache === null) {
- _retryCache = instance._retryCache = new PossiblyWeakSet();
+ if (_flags3 & ShouldCapture) {
+ workInProgress.flags = _flags3 & ~ShouldCapture | DidCapture; // Captured a suspense effect. Re-render the boundary.
+
+ if ((workInProgress.mode & ProfileMode) !== NoMode) {
+ transferActualDuration(workInProgress);
+ }
+
+ return workInProgress;
}
- return _retryCache;
+ return null;
}
- default:
+ case CacheComponent:
{
- throw new Error("Unexpected Suspense handler tag (" + finishedWork.tag + "). This is a " + 'bug in React.');
+ popCacheProvider(workInProgress);
}
- }
-}
-
-function detachOffscreenInstance(instance) {
- var fiber = instance._current;
-
- if (fiber === null) {
- throw new Error('Calling Offscreen.detach before instance handle has been set.');
- }
- if ((instance._pendingVisibility & OffscreenDetached) !== NoFlags$1) {
- // The instance is already detached, this is a noop.
- return;
- } // TODO: There is an opportunity to optimise this by not entering commit phase
- // and unmounting effects directly.
+ return null;
+ case TracingMarkerComponent:
- var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
+ return null;
- if (root !== null) {
- instance._pendingVisibility |= OffscreenDetached;
- scheduleUpdateOnFiber(root, fiber, SyncLane);
+ default:
+ return null;
}
}
-function attachOffscreenInstance(instance) {
- var fiber = instance._current;
-
- if (fiber === null) {
- throw new Error('Calling Offscreen.detach before instance handle has been set.');
- }
- if ((instance._pendingVisibility & OffscreenDetached) === NoFlags$1) {
- // The instance is already attached, this is a noop.
- return;
- }
+function unwindInterruptedWork(current, interruptedWork, renderLanes) {
+ // Note: This intentionally doesn't check if we're hydrating because comparing
+ // to the current tree provider fiber is just as fast and less error-prone.
+ // Ideally we would have a special version of the work loop only
+ // for hydration.
+ popTreeContext(interruptedWork);
- var root = enqueueConcurrentRenderForLane(fiber, SyncLane);
+ switch (interruptedWork.tag) {
+ case ClassComponent:
+ {
- if (root !== null) {
- instance._pendingVisibility &= ~OffscreenDetached;
- scheduleUpdateOnFiber(root, fiber, SyncLane);
- }
-}
+ break;
+ }
-function attachSuspenseRetryListeners(finishedWork, wakeables) {
- // If this boundary just timed out, then it will have a set of wakeables.
- // For each wakeable, attach a listener so that when it resolves, React
- // attempts to re-render the boundary in the primary (pre-timeout) state.
- var retryCache = getRetryCache(finishedWork);
- wakeables.forEach(function (wakeable) {
- // Memoize using the boundary fiber to prevent redundant listeners.
- var retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
+ case HostRoot:
+ {
- if (!retryCache.has(wakeable)) {
- retryCache.add(wakeable);
+ {
+ popCacheProvider(interruptedWork);
+ }
+ popHostContainer(interruptedWork);
+ break;
+ }
+ case HostHoistable:
+ case HostSingleton:
+ case HostComponent:
{
- if (isDevToolsPresent) {
- if (inProgressLanes !== null && inProgressRoot !== null) {
- // If we have pending work still, associate the original updaters with it.
- restorePendingUpdaters(inProgressRoot, inProgressLanes);
- } else {
- throw Error('Expected finished root and lanes to be set. This is a bug in React.');
- }
- }
+ popHostContext(interruptedWork);
+ break;
}
- wakeable.then(retry, retry);
- }
- });
-} // This function detects when a Suspense boundary goes from visible to hidden.
-function commitMutationEffects(root, finishedWork, committedLanes) {
- inProgressLanes = committedLanes;
- inProgressRoot = root;
- setCurrentFiber(finishedWork);
- commitMutationEffectsOnFiber(finishedWork, root);
- setCurrentFiber(finishedWork);
- inProgressLanes = null;
- inProgressRoot = null;
-}
+ case HostPortal:
+ popHostContainer(interruptedWork);
+ break;
-function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects hae fired.
- var deletions = parentFiber.deletions;
+ case SuspenseComponent:
+ popSuspenseHandler(interruptedWork);
+ break;
- if (deletions !== null) {
- for (var i = 0; i < deletions.length; i++) {
- var childToDelete = deletions[i];
+ case SuspenseListComponent:
+ popSuspenseListContext(interruptedWork);
+ break;
- try {
- commitDeletionEffects(root, parentFiber, childToDelete);
- } catch (error) {
- captureCommitPhaseError(childToDelete, parentFiber, error);
+ case ContextProvider:
+ var context;
+
+ {
+ context = interruptedWork.type;
}
- }
- }
- var prevDebugFiber = getCurrentFiber();
+ popProvider(context, interruptedWork);
+ break;
- if (parentFiber.subtreeFlags & MutationMask) {
- var child = parentFiber.child;
+ case OffscreenComponent:
+ case LegacyHiddenComponent:
+ popSuspenseHandler(interruptedWork);
+ popHiddenContext(interruptedWork);
+ popTransition(interruptedWork, current);
+ break;
- while (child !== null) {
- setCurrentFiber(child);
- commitMutationEffectsOnFiber(child, root);
- child = child.sibling;
- }
- }
+ case CacheComponent:
+ {
+ popCacheProvider(interruptedWork);
+ }
- setCurrentFiber(prevDebugFiber);
+ break;
+ }
}
-var currentHoistableRoot = null;
-
-function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
- var current = finishedWork.alternate;
- var flags = finishedWork.flags; // The effect flag should be checked *after* we refine the type of fiber,
- // because the fiber tag is more specific. An exception is any flag related
- // to reconciliation, because those can be set on all fiber types.
+function getCacheForType(resourceType) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+ var cache = readContext(CacheContext);
+ var cacheForType = cache.data.get(resourceType);
- if (flags & Update) {
- try {
- commitHookEffectListUnmount(Insertion | HasEffect, finishedWork, finishedWork.return);
- commitHookEffectListMount(Insertion | HasEffect, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- } // Layout effects are destroyed during the mutation phase so that all
- // destroy functions for all fibers are called before any create functions.
- // This prevents sibling component effects from interfering with each other,
- // e.g. a destroy function in one component should never override a ref set
- // by a create function in another component during the same commit.
+ if (cacheForType === undefined) {
+ cacheForType = resourceType();
+ cache.data.set(resourceType, cacheForType);
+ }
+ return cacheForType;
+}
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
+var DefaultAsyncDispatcher = {
+ getCacheForType: getCacheForType
+};
- recordLayoutEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
+{
+ DefaultAsyncDispatcher.getOwner = function () {
+ return currentOwner;
+ };
+}
- return;
- }
+if (typeof Symbol === 'function' && Symbol.for) {
+ var symbolFor = Symbol.for;
+ symbolFor('selector.component');
+ symbolFor('selector.has_pseudo_class');
+ symbolFor('selector.role');
+ symbolFor('selector.test_id');
+ symbolFor('selector.text');
+}
+var commitHooks = [];
+function onCommitRoot() {
+ {
+ commitHooks.forEach(function (commitHook) {
+ return commitHook();
+ });
+ }
+}
- case ClassComponent:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+function isConcurrentActEnvironment() {
+ {
+ var isReactActEnvironmentGlobal = // $FlowFixMe[cannot-resolve-name] Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
+ typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined' ? // $FlowFixMe[cannot-resolve-name]
+ IS_REACT_ACT_ENVIRONMENT : undefined;
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
+ if (!isReactActEnvironmentGlobal && ReactSharedInternals.actQueue !== null) {
+ // TODO: Include link to relevant documentation page.
+ error('The current testing environment is not configured to support ' + 'act(...)');
+ }
- if (flags & Callback && offscreenSubtreeIsHidden) {
- var updateQueue = finishedWork.updateQueue;
+ return isReactActEnvironmentGlobal;
+ }
+}
- if (updateQueue !== null) {
- deferHiddenCallbacks(updateQueue);
- }
- }
+var PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
+var NoContext =
+/* */
+0;
+var RenderContext =
+/* */
+2;
+var CommitContext =
+/* */
+4;
+var RootInProgress = 0;
+var RootFatalErrored = 1;
+var RootErrored = 2;
+var RootSuspended = 3;
+var RootSuspendedWithDelay = 4;
+var RootCompleted = 5;
+var RootDidNotComplete = 6; // Describes where we are in the React execution stack
- return;
- }
+var executionContext = NoContext; // The root we're working on
- case HostHoistable:
- {
- {
- // We cast because we always set the root at the React root and so it cannot be
- // null while we are processing mutation effects
- var hoistableRoot = currentHoistableRoot;
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+var workInProgressRoot = null; // The fiber we're working on
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
+var workInProgress = null; // The lanes we're rendering
- if (flags & Update) {
- var currentResource = current !== null ? current.memoizedState : null;
- var newResource = finishedWork.memoizedState;
+var workInProgressRootRenderLanes = NoLanes;
+var NotSuspended = 0;
+var SuspendedOnError = 1;
+var SuspendedOnData = 2;
+var SuspendedOnImmediate = 3;
+var SuspendedOnInstance = 4;
+var SuspendedOnInstanceAndReadyToContinue = 5;
+var SuspendedOnDeprecatedThrowPromise = 6;
+var SuspendedAndReadyToContinue = 7;
+var SuspendedOnHydration = 8; // When this is true, the work-in-progress fiber just suspended (or errored) and
+// we've yet to unwind the stack. In some cases, we may yield to the main thread
+// after this happens. If the fiber is pinged before we resume, we can retry
+// immediately instead of unwinding the stack.
- if (current === null) {
- // We are mounting a new HostHoistable Fiber. We fork the mount
- // behavior based on whether this instance is a Hoistable Instance
- // or a Hoistable Resource
- if (newResource === null) {
- if (finishedWork.stateNode === null) {
- finishedWork.stateNode = hydrateHoistable(hoistableRoot, finishedWork.type, finishedWork.memoizedProps, finishedWork);
- } else {
- mountHoistable(hoistableRoot, finishedWork.type, finishedWork.stateNode);
- }
- } else {
- finishedWork.stateNode = acquireResource(hoistableRoot, newResource, finishedWork.memoizedProps);
- }
- } else if (currentResource !== newResource) {
- // We are moving to or from Hoistable Resource, or between different Hoistable Resources
- if (currentResource === null) {
- if (current.stateNode !== null) {
- unmountHoistable(current.stateNode);
- }
- } else {
- releaseResource(currentResource);
- }
+var workInProgressSuspendedReason = NotSuspended;
+var workInProgressThrownValue = null; // Whether a ping listener was attached during this render. This is slightly
+// different that whether something suspended, because we don't add multiple
+// listeners to a promise we've already seen (per root and lane).
- if (newResource === null) {
- mountHoistable(hoistableRoot, finishedWork.type, finishedWork.stateNode);
- } else {
- acquireResource(hoistableRoot, newResource, finishedWork.memoizedProps);
- }
- } else if (newResource === null && finishedWork.stateNode !== null) {
- try {
- commitUpdate(finishedWork.stateNode, finishedWork.type, current.memoizedProps, finishedWork.memoizedProps, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
+var workInProgressRootDidAttachPingListener = false; // A contextual version of workInProgressRootRenderLanes. It is a superset of
+// the lanes that we started working on at the root. When we enter a subtree
+// that is currently hidden, we add the lanes that would have committed if
+// the hidden tree hadn't been deferred. This is modified by the
+// HiddenContext module.
+//
+// Most things in the work loop should deal with workInProgressRootRenderLanes.
+// Most things in begin/complete phases should deal with entangledRenderLanes.
- return;
- } // Fall through
+var entangledRenderLanes = NoLanes; // Whether to root completed, errored, suspended, etc.
- }
+var workInProgressRootExitStatus = RootInProgress; // The work left over by components that were visited during this render. Only
+// includes unprocessed updates, not work in bailed out children.
- case HostSingleton:
- {
- {
- if (flags & Update) {
- var previousWork = finishedWork.alternate;
+var workInProgressRootSkippedLanes = NoLanes; // Lanes that were updated (in an interleaved event) during this render.
- if (previousWork === null) {
- var singleton = finishedWork.stateNode;
- var props = finishedWork.memoizedProps; // This was a new mount, we need to clear and set initial properties
+var workInProgressRootInterleavedUpdatedLanes = NoLanes; // Lanes that were updated during the render phase (*not* an interleaved event).
- clearSingleton(singleton);
- acquireSingletonInstance(finishedWork.type, props, singleton, finishedWork);
- }
- }
- } // Fall through
+var workInProgressRootPingedLanes = NoLanes; // If this lane scheduled deferred work, this is the lane of the deferred task.
- }
+var workInProgressDeferredLane = NoLane; // Errors that are thrown during the render phase.
- case HostComponent:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+var workInProgressRootConcurrentErrors = null; // These are errors that we recovered from without surfacing them to the UI.
+// We will log them once the tree commits.
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
+var workInProgressRootRecoverableErrors = null; // Tracks when an update occurs during the render phase.
- {
- // TODO: ContentReset gets cleared by the children during the commit
- // phase. This is a refactor hazard because it means we must read
- // flags the flags after `commitReconciliationEffects` has already run;
- // the order matters. We should refactor so that ContentReset does not
- // rely on mutating the flag during commit. Like by setting a flag
- // during the render phase instead.
- if (finishedWork.flags & ContentReset) {
- var instance = finishedWork.stateNode;
+var workInProgressRootDidIncludeRecursiveRenderUpdate = false; // Thacks when an update occurs during the commit phase. It's a separate
+// variable from the one for renders because the commit phase may run
+// concurrently to a render phase.
- try {
- resetTextContent(instance);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
+var didIncludeCommitPhaseUpdate = false; // The most recent time we either committed a fallback, or when a fallback was
+// filled in with the resolved UI. This lets us throttle the appearance of new
+// content as it streams in, to minimize jank.
+// TODO: Think of a better name for this variable?
- if (flags & Update) {
- var _instance2 = finishedWork.stateNode;
+var globalMostRecentFallbackTime = 0;
+var FALLBACK_THROTTLE_MS = 300; // The absolute time for when we should start giving up on rendering
+// more and prefer CPU suspense heuristics instead.
- if (_instance2 != null) {
- // Commit the work prepared earlier.
- var newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
- // as the newProps. The updatePayload will contain the real change in
- // this case.
+var workInProgressRootRenderTargetTime = Infinity; // How long a render is supposed to take before we start following CPU
+// suspense heuristics and opt out of rendering more content.
- var oldProps = current !== null ? current.memoizedProps : newProps;
- var type = finishedWork.type;
+var RENDER_TIMEOUT_MS = 500;
+var workInProgressTransitions = null;
- try {
- commitUpdate(_instance2, type, oldProps, newProps, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
+function resetRenderTimer() {
+ workInProgressRootRenderTargetTime = now$1() + RENDER_TIMEOUT_MS;
+}
- if (flags & FormReset) {
- needsFormReset = true;
+function getRenderTargetTime() {
+ return workInProgressRootRenderTargetTime;
+}
+var legacyErrorBoundariesThatAlreadyFailed = null;
+var rootDoesHavePassiveEffects = false;
+var rootWithPendingPassiveEffects = null;
+var pendingPassiveEffectsLanes = NoLanes;
+var pendingPassiveProfilerEffects = [];
+var pendingPassiveEffectsRemainingLanes = NoLanes;
+var pendingPassiveTransitions = null; // Use these to prevent an infinite loop of nested updates
- {
- if (finishedWork.type !== 'form') {
- // Paranoid coding. In case we accidentally start using the
- // FormReset bit for something else.
- error('Unexpected host component type. Expected a form. This is a ' + 'bug in React.');
- }
- }
- }
- }
+var NESTED_UPDATE_LIMIT = 50;
+var nestedUpdateCount = 0;
+var rootWithNestedUpdates = null;
+var isFlushingPassiveEffects = false;
+var didScheduleUpdateDuringPassiveEffects = false;
+var NESTED_PASSIVE_UPDATE_LIMIT = 50;
+var nestedPassiveUpdateCount = 0;
+var rootWithPassiveNestedUpdates = null;
+var isRunningInsertionEffect = false;
+function getWorkInProgressRoot() {
+ return workInProgressRoot;
+}
+function getWorkInProgressRootRenderLanes() {
+ return workInProgressRootRenderLanes;
+}
+function isWorkLoopSuspendedOnData() {
+ return workInProgressSuspendedReason === SuspendedOnData;
+}
+function requestUpdateLane(fiber) {
+
+ if ((executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes) {
+ // This is a render phase update. These are not officially supported. The
+ // old behavior is to give this the same "thread" (lanes) as
+ // whatever is currently rendering. So if you call `setState` on a component
+ // that happens later in the same render, it will flush. Ideally, we want to
+ // remove the special case and treat them as if they came from an
+ // interleaved event. Regardless, this pattern is not officially supported.
+ // This behavior is only a fallback. The flag only exists until we can roll
+ // out the setState warning, since existing code might accidentally rely on
+ // the current behavior.
+ return pickArbitraryLane(workInProgressRootRenderLanes);
+ }
- return;
+ var transition = requestCurrentTransition();
+
+ if (transition !== null) {
+ {
+ if (!transition._updatedFibers) {
+ transition._updatedFibers = new Set();
}
- case HostText:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+ transition._updatedFibers.add(fiber);
+ }
- if (flags & Update) {
- {
- if (finishedWork.stateNode === null) {
- throw new Error('This should have a text node initialized. This error is likely ' + 'caused by a bug in React. Please file an issue.');
- }
+ var actionScopeLane = peekEntangledActionLane();
+ return actionScopeLane !== NoLane ? // We're inside an async action scope. Reuse the same lane.
+ actionScopeLane : // We may or may not be inside an async action scope. If we are, this
+ // is the first update in that scope. Either way, we need to get a
+ // fresh transition lane.
+ requestTransitionLane();
+ }
- var textInstance = finishedWork.stateNode;
- var newText = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps
- // as the newProps. The updatePayload will contain the real change in
- // this case.
+ return eventPriorityToLane(resolveUpdatePriority());
+}
- var oldText = current !== null ? current.memoizedProps : newText;
+function requestRetryLane(fiber) {
- try {
- commitTextUpdate(textInstance, oldText, newText);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
+ return claimNextRetryLane();
+}
- return;
- }
+function requestDeferredLane() {
+ if (workInProgressDeferredLane === NoLane) {
+ // If there are multiple useDeferredValue hooks in the same render, the
+ // tasks that they spawn should all be batched together, so they should all
+ // receive the same lane.
+ // Check the priority of the current render to decide the priority of the
+ // deferred task.
+ // OffscreenLane is used for prerendering, but we also use OffscreenLane
+ // for incremental hydration. It's given the lowest priority because the
+ // initial HTML is the same as the final UI. But useDeferredValue during
+ // hydration is an exception — we need to upgrade the UI to the final
+ // value. So if we're currently hydrating, we treat it like a transition.
+ var isPrerendering = includesSomeLane(workInProgressRootRenderLanes, OffscreenLane) && !getIsHydrating();
- case HostRoot:
- {
- {
- prepareToCommitHoistables();
- var previousHoistableRoot = currentHoistableRoot;
- currentHoistableRoot = getHoistableRoot(root.containerInfo);
- recursivelyTraverseMutationEffects(root, finishedWork);
- currentHoistableRoot = previousHoistableRoot;
- commitReconciliationEffects(finishedWork);
- }
+ if (isPrerendering) {
+ // There's only one OffscreenLane, so if it contains deferred work, we
+ // should just reschedule using the same lane.
+ workInProgressDeferredLane = OffscreenLane;
+ } else {
+ // Everything else is spawned as a transition.
+ workInProgressDeferredLane = claimNextTransitionLane();
+ }
+ } // Mark the parent Suspense boundary so it knows to spawn the deferred lane.
- if (flags & Update) {
- {
- if (current !== null) {
- var prevRootState = current.memoizedState;
- if (prevRootState.isDehydrated) {
- try {
- commitHydratedContainer(root.containerInfo);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- }
- }
- }
- }
+ var suspenseHandler = getSuspenseHandler();
- if (needsFormReset) {
- // A form component requested to be reset during this commit. We do this
- // after all mutations in the rest of the tree so that `defaultValue`
- // will already be updated. This way you can update `defaultValue` using
- // data sent by the server as a result of the form submission.
- //
- // Theoretically we could check finishedWork.subtreeFlags & FormReset,
- // but the FormReset bit is overloaded with other flags used by other
- // fiber types. So this extra variable lets us skip traversing the tree
- // except when a form was actually submitted.
- needsFormReset = false;
- recursivelyResetForms(finishedWork);
- }
+ if (suspenseHandler !== null) {
+ // TODO: As an optimization, we shouldn't entangle the lanes at the root; we
+ // can entangle them using the baseLanes of the Suspense boundary instead.
+ // We only need to do something special if there's no Suspense boundary.
+ suspenseHandler.flags |= DidDefer;
+ }
- return;
- }
+ return workInProgressDeferredLane;
+}
+function peekDeferredLane() {
+ return workInProgressDeferredLane;
+}
+function scheduleUpdateOnFiber(root, fiber, lane) {
+ {
+ if (isRunningInsertionEffect) {
+ error('useInsertionEffect must not schedule updates.');
+ }
+ }
- case HostPortal:
- {
- {
- var _previousHoistableRoot = currentHoistableRoot;
- currentHoistableRoot = getHoistableRoot(finishedWork.stateNode.containerInfo);
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
- currentHoistableRoot = _previousHoistableRoot;
- }
+ {
+ if (isFlushingPassiveEffects) {
+ didScheduleUpdateDuringPassiveEffects = true;
+ }
+ } // Check if the work loop is currently suspended and waiting for data to
+ // finish loading.
- return;
- }
- case SuspenseComponent:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork); // TODO: We should mark a flag on the Suspense fiber itself, rather than
- // relying on the Offscreen fiber having a flag also being marked. The
- // reason is that this offscreen fiber might not be part of the work-in-
- // progress tree! It could have been reused from a previous render. This
- // doesn't lead to incorrect behavior because we don't rely on the flag
- // check alone; we also compare the states explicitly below. But for
- // modeling purposes, we _should_ be able to rely on the flag check alone.
- // So this is a bit fragile.
- //
- // Also, all this logic could/should move to the passive phase so it
- // doesn't block paint.
+ if ( // Suspended render phase
+ root === workInProgressRoot && workInProgressSuspendedReason === SuspendedOnData || // Suspended commit phase
+ root.cancelPendingCommit !== null) {
+ // The incoming update might unblock the current render. Interrupt the
+ // current attempt and restart from the top.
+ prepareFreshStack(root, NoLanes);
+ markRootSuspended(root, workInProgressRootRenderLanes, workInProgressDeferredLane);
+ } // Mark that the root has a pending update.
- var offscreenFiber = finishedWork.child;
- if (offscreenFiber.flags & Visibility) {
- // Throttle the appearance and disappearance of Suspense fallbacks.
- var isShowingFallback = finishedWork.memoizedState !== null;
- var wasShowingFallback = current !== null && current.memoizedState !== null;
+ markRootUpdated(root, lane);
- {
- if (isShowingFallback !== wasShowingFallback) {
- // A fallback is either appearing or disappearing.
- markCommitTimeOfFallback();
- }
- }
- }
+ if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {
+ // This update was dispatched during the render phase. This is a mistake
+ // if the update originates from user space (with the exception of local
+ // hook updates, which are handled differently and don't reach this
+ // function), but there are some internal React features that use this as
+ // an implementation detail, like selective hydration.
+ warnAboutRenderPhaseUpdatesInDEV(fiber); // Track lanes that were updated during the render phase
+ } else {
+ // This is a normal update, scheduled from outside the render phase. For
+ // example, during an input event.
+ {
+ if (isDevToolsPresent) {
+ addFiberToLanesMap(root, fiber, lane);
+ }
+ }
- if (flags & Update) {
- try {
- commitSuspenseCallback(finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
+ warnIfUpdatesNotWrappedWithActDEV(fiber);
- var retryQueue = finishedWork.updateQueue;
+ if (root === workInProgressRoot) {
+ // Received an update to a tree that's in the middle of rendering. Mark
+ // that there was an interleaved update work on this root.
+ if ((executionContext & RenderContext) === NoContext) {
+ workInProgressRootInterleavedUpdatedLanes = mergeLanes(workInProgressRootInterleavedUpdatedLanes, lane);
+ }
- if (retryQueue !== null) {
- finishedWork.updateQueue = null;
- attachSuspenseRetryListeners(finishedWork, retryQueue);
- }
- }
+ if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
+ // The root already suspended with a delay, which means this render
+ // definitely won't finish. Since we have a new update, let's mark it as
+ // suspended now, right before marking the incoming update. This has the
+ // effect of interrupting the current render and switching to the update.
+ // TODO: Make sure this doesn't override pings that happen while we've
+ // already started rendering.
+ markRootSuspended(root, workInProgressRootRenderLanes, workInProgressDeferredLane);
+ }
+ }
- return;
+ ensureRootIsScheduled(root);
+
+ if (lane === SyncLane && executionContext === NoContext && !disableLegacyMode && (fiber.mode & ConcurrentMode) === NoMode) {
+ if (ReactSharedInternals.isBatchingLegacy) ; else {
+ // Flush the synchronous work now, unless we're already working or inside
+ // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
+ // scheduleCallbackForFiber to preserve the ability to schedule a callback
+ // without immediately flushing it. We only do this for user-initiated
+ // updates, to preserve historical behavior of legacy mode.
+ resetRenderTimer();
}
+ }
+ }
+}
+function scheduleInitialHydrationOnRoot(root, lane) {
+ // This is a special fork of scheduleUpdateOnFiber that is only used to
+ // schedule the initial hydration of a root that has just been created. Most
+ // of the stuff in scheduleUpdateOnFiber can be skipped.
+ //
+ // The main reason for this separate path, though, is to distinguish the
+ // initial children from subsequent updates. In fully client-rendered roots
+ // (createRoot instead of hydrateRoot), all top-level renders are modeled as
+ // updates, but hydration roots are special because the initial render must
+ // match what was rendered on the server.
+ var current = root.current;
+ current.lanes = lane;
+ markRootUpdated(root, lane);
+ ensureRootIsScheduled(root);
+}
+function isUnsafeClassRenderPhaseUpdate(fiber) {
+ // Check if this is a render phase update. Only called by class components,
+ // which special (deprecated) behavior for UNSAFE_componentWillReceive props.
+ return (executionContext & RenderContext) !== NoContext;
+} // This is the entry point for every concurrent task, i.e. anything that
+// goes through Scheduler.
- case OffscreenComponent:
- {
- if (flags & Ref) {
- if (current !== null) {
- safelyDetachRef(current, current.return);
- }
- }
+function performConcurrentWorkOnRoot(root, didTimeout) {
+ {
+ resetNestedUpdateFlag();
+ }
- var newState = finishedWork.memoizedState;
- var isHidden = newState !== null;
- var wasHidden = current !== null && current.memoizedState !== null;
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
+ throw new Error('Should not already be working.');
+ } // Flush any pending passive effects before deciding which lanes to work on,
+ // in case they schedule additional work.
- {
- // Before committing the children, track on the stack whether this
- // offscreen subtree was already hidden, so that we don't unmount the
- // effects again.
- var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
- var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
- recursivelyTraverseMutationEffects(root, finishedWork);
- offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
- offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
- }
- commitReconciliationEffects(finishedWork);
- var offscreenInstance = finishedWork.stateNode; // TODO: Add explicit effect flag to set _current.
+ var originalCallbackNode = root.callbackNode;
+ var didFlushPassiveEffects = flushPassiveEffects();
- offscreenInstance._current = finishedWork; // Offscreen stores pending changes to visibility in `_pendingVisibility`. This is
- // to support batching of `attach` and `detach` calls.
+ if (didFlushPassiveEffects) {
+ // Something in the passive effect phase may have canceled the current task.
+ // Check if the task node for this root was changed.
+ if (root.callbackNode !== originalCallbackNode) {
+ // The current task was canceled. Exit. We don't need to call
+ // `ensureRootIsScheduled` because the check above implies either that
+ // there's a new task, or that there's no remaining work on this root.
+ return null;
+ }
+ } // Determine the next lanes to work on, using the fields stored
+ // on the root.
+ // TODO: This was already computed in the caller. Pass it as an argument.
- offscreenInstance._visibility &= ~OffscreenDetached;
- offscreenInstance._visibility |= offscreenInstance._pendingVisibility & OffscreenDetached;
- if (flags & Visibility) {
- // Track the current state on the Offscreen instance so we can
- // read it during an event
- if (isHidden) {
- offscreenInstance._visibility &= ~OffscreenVisible;
- } else {
- offscreenInstance._visibility |= OffscreenVisible;
- }
+ var lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
- if (isHidden) {
- var isUpdate = current !== null;
- var wasHiddenByAncestorOffscreen = offscreenSubtreeIsHidden || offscreenSubtreeWasHidden; // Only trigger disapper layout effects if:
- // - This is an update, not first mount.
- // - This Offscreen was not hidden before.
- // - Ancestor Offscreen was not hidden in previous commit.
+ if (lanes === NoLanes) {
+ // Defensive coding. This is never expected to happen.
+ return null;
+ } // We disable time-slicing in some cases: if the work has been CPU-bound
+ // for too long ("expired" work, to prevent starvation), or we're in
+ // sync-updates-by-default mode.
+ // TODO: We only check `didTimeout` defensively, to account for a Scheduler
+ // bug we're still investigating. Once the bug in Scheduler is fixed,
+ // we can remove this, since we track expiration ourselves.
- if (isUpdate && !wasHidden && !wasHiddenByAncestorOffscreen) {
- {
- // Disappear the layout effects of all the children
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- }
- }
- } // Offscreen with manual mode manages visibility manually.
+ var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && (!didTimeout);
+ var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
- if (!isOffscreenManual(finishedWork)) {
- // TODO: This needs to run whenever there's an insertion or update
- // inside a hidden Offscreen tree.
- hideOrUnhideAllChildren(finishedWork, isHidden);
- }
- } // TODO: Move to passive phase
+ if (exitStatus !== RootInProgress) {
+ var renderWasConcurrent = shouldTimeSlice;
+ do {
+ if (exitStatus === RootDidNotComplete) {
+ // The render unwound without completing the tree. This happens in special
+ // cases where need to exit the current render without producing a
+ // consistent tree or committing.
+ markRootSuspended(root, lanes, NoLane);
+ } else {
+ // The render completed.
+ // Check if this render may have yielded to a concurrent event, and if so,
+ // confirm that any newly rendered stores are consistent.
+ // TODO: It's possible that even a concurrent render may never have yielded
+ // to the main thread, if it was fast enough, or if it expired. We could
+ // skip the consistency check in that case, too.
+ var finishedWork = root.current.alternate;
- if (flags & Update) {
- var offscreenQueue = finishedWork.updateQueue;
+ if (renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork)) {
+ // A store was mutated in an interleaved event. Render again,
+ // synchronously, to block further mutations.
+ exitStatus = renderRootSync(root, lanes); // We assume the tree is now consistent because we didn't yield to any
+ // concurrent events.
- if (offscreenQueue !== null) {
- var _retryQueue = offscreenQueue.retryQueue;
+ renderWasConcurrent = false; // Need to check the exit status again.
- if (_retryQueue !== null) {
- offscreenQueue.retryQueue = null;
- attachSuspenseRetryListeners(finishedWork, _retryQueue);
- }
- }
- }
+ continue;
+ } // Check if something threw
- return;
- }
- case SuspenseListComponent:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
+ if (exitStatus === RootErrored) {
+ var lanesThatJustErrored = lanes;
+ var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root, lanesThatJustErrored);
- if (flags & Update) {
- var _retryQueue2 = finishedWork.updateQueue;
+ if (errorRetryLanes !== NoLanes) {
+ lanes = errorRetryLanes;
+ exitStatus = recoverFromConcurrentError(root, lanesThatJustErrored, errorRetryLanes);
+ renderWasConcurrent = false; // Need to check the exit status again.
- if (_retryQueue2 !== null) {
- finishedWork.updateQueue = null;
- attachSuspenseRetryListeners(finishedWork, _retryQueue2);
+ if (exitStatus !== RootErrored) {
+ // The root did not error this time. Restart the exit algorithm
+ // from the beginning.
+ // TODO: Refactor the exit algorithm to be less confusing. Maybe
+ // more branches + recursion instead of a loop. I think the only
+ // thing that causes it to be a loop is the RootDidNotComplete
+ // check. If that's true, then we don't need a loop/recursion
+ // at all.
+ continue;
+ }
}
}
- return;
- }
-
- case ScopeComponent:
- {
+ if (exitStatus === RootFatalErrored) {
+ prepareFreshStack(root, NoLanes);
+ markRootSuspended(root, lanes, NoLane);
+ break;
+ } // We now have a consistent tree. The next step is either to commit it,
+ // or, if something suspended, wait to commit it after a timeout.
- return;
- }
- default:
- {
- recursivelyTraverseMutationEffects(root, finishedWork);
- commitReconciliationEffects(finishedWork);
- return;
+ root.finishedWork = finishedWork;
+ root.finishedLanes = lanes;
+ finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
- }
-}
-
-function commitReconciliationEffects(finishedWork) {
- // Placement effects (insertions, reorders) can be scheduled on any fiber
- // type. They needs to happen after the children effects have fired, but
- // before the effects on this fiber have fired.
- var flags = finishedWork.flags;
-
- if (flags & Placement) {
- try {
- commitPlacement(finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- } // Clear the "placement" from effect tag so that we know that this is
- // inserted, before any life-cycles like componentDidMount gets called.
- // TODO: findDOMNode doesn't rely on this any more but isMounted does
- // and isMounted is deprecated anyway so we should be able to kill this.
-
- finishedWork.flags &= ~Placement;
+ break;
+ } while (true);
}
- if (flags & Hydrating) {
- finishedWork.flags &= ~Hydrating;
- }
+ ensureRootIsScheduled(root);
+ return getContinuationForRoot(root, originalCallbackNode);
}
-function recursivelyResetForms(parentFiber) {
- if (parentFiber.subtreeFlags & FormReset) {
- var child = parentFiber.child;
+function recoverFromConcurrentError(root, originallyAttemptedLanes, errorRetryLanes) {
+ // If an error occurred during hydration, discard server response and fall
+ // back to client side render.
+ // Before rendering again, save the errors from the previous attempt.
+ var errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
+ var wasRootDehydrated = isRootDehydrated(root);
- while (child !== null) {
- resetFormOnFiber(child);
- child = child.sibling;
- }
+ if (wasRootDehydrated) {
+ // The shell failed to hydrate. Set a flag to force a client rendering
+ // during the next attempt. To do this, we call prepareFreshStack now
+ // to create the root work-in-progress fiber. This is a bit weird in terms
+ // of factoring, because it relies on renderRootSync not calling
+ // prepareFreshStack again in the call below, which happens because the
+ // root and lanes haven't changed.
+ //
+ // TODO: I think what we should do is set ForceClientRender inside
+ // throwException, like we do for nested Suspense boundaries. The reason
+ // it's here instead is so we can switch to the synchronous work loop, too.
+ // Something to consider for a future refactor.
+ var rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
+ rootWorkInProgress.flags |= ForceClientRender;
}
-}
-function resetFormOnFiber(fiber) {
- recursivelyResetForms(fiber);
+ var exitStatus = renderRootSync(root, errorRetryLanes);
- if (fiber.tag === HostComponent && fiber.flags & FormReset) {
- var formInstance = fiber.stateNode;
- resetFormInstance(formInstance);
- }
-}
+ if (exitStatus !== RootErrored) {
+ // Successfully finished rendering on retry
+ if (workInProgressRootDidAttachPingListener && !wasRootDehydrated) {
+ // During the synchronous render, we attached additional ping listeners.
+ // This is highly suggestive of an uncached promise (though it's not the
+ // only reason this would happen). If it was an uncached promise, then
+ // it may have masked a downstream error from ocurring without actually
+ // fixing it. Example:
+ //
+ // use(Promise.resolve('uncached'))
+ // throw new Error('Oops!')
+ //
+ // When this happens, there's a conflict between blocking potential
+ // concurrent data races and unwrapping uncached promise values. We
+ // have to choose one or the other. Because the data race recovery is
+ // a last ditch effort, we'll disable it.
+ root.errorRecoveryDisabledLanes = mergeLanes(root.errorRecoveryDisabledLanes, originallyAttemptedLanes); // Mark the current render as suspended and force it to restart. Once
+ // these lanes finish successfully, we'll re-enable the error recovery
+ // mechanism for subsequent updates.
-function commitLayoutEffects(finishedWork, root, committedLanes) {
- inProgressLanes = committedLanes;
- inProgressRoot = root;
- var current = finishedWork.alternate;
- commitLayoutEffectOnFiber(root, current, finishedWork);
- inProgressLanes = null;
- inProgressRoot = null;
-}
+ workInProgressRootInterleavedUpdatedLanes |= originallyAttemptedLanes;
+ return RootSuspendedWithDelay;
+ } // The errors from the failed first attempt have been recovered. Add
+ // them to the collection of recoverable errors. We'll log them in the
+ // commit phase.
-function recursivelyTraverseLayoutEffects(root, parentFiber, lanes) {
- var prevDebugFiber = getCurrentFiber();
- if (parentFiber.subtreeFlags & LayoutMask) {
- var child = parentFiber.child;
+ var errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
+ workInProgressRootRecoverableErrors = errorsFromFirstAttempt; // The errors from the second attempt should be queued after the errors
+ // from the first attempt, to preserve the causal sequence.
- while (child !== null) {
- setCurrentFiber(child);
- var current = child.alternate;
- commitLayoutEffectOnFiber(root, current, child);
- child = child.sibling;
+ if (errorsFromSecondAttempt !== null) {
+ queueRecoverableErrors(errorsFromSecondAttempt);
}
}
- setCurrentFiber(prevDebugFiber);
+ return exitStatus;
}
-function disappearLayoutEffects(finishedWork) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case MemoComponent:
- case SimpleMemoComponent:
- {
- // TODO (Offscreen) Check: flags & LayoutStatic
- if (shouldProfile(finishedWork)) {
- try {
- startLayoutEffectTimer();
- commitHookEffectListUnmount(Layout, finishedWork, finishedWork.return);
- } finally {
- recordLayoutEffectDuration(finishedWork);
- }
- } else {
- commitHookEffectListUnmount(Layout, finishedWork, finishedWork.return);
- }
+function queueRecoverableErrors(errors) {
+ if (workInProgressRootRecoverableErrors === null) {
+ workInProgressRootRecoverableErrors = errors;
+ } else {
+ // $FlowFixMe[method-unbinding]
+ workInProgressRootRecoverableErrors.push.apply(workInProgressRootRecoverableErrors, errors);
+ }
+}
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
+function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
+ // TODO: The fact that most of these branches are identical suggests that some
+ // of the exit statuses are not best modeled as exit statuses and should be
+ // tracked orthogonally.
+ switch (exitStatus) {
+ case RootInProgress:
+ case RootFatalErrored:
+ {
+ throw new Error('Root did not complete. This is a bug in React.');
}
- case ClassComponent:
+ case RootSuspendedWithDelay:
{
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
- var instance = finishedWork.stateNode;
+ if (includesOnlyTransitions(lanes)) {
+ // This is a transition, so we should exit without committing a
+ // placeholder and without scheduling a timeout. Delay indefinitely
+ // until we receive more data.
+ markRootSuspended(root, lanes, workInProgressDeferredLane);
+ return;
+ } // Commit the placeholder.
- if (typeof instance.componentWillUnmount === 'function') {
- safelyCallComponentWillUnmount(finishedWork, finishedWork.return, instance);
- }
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
- case HostHoistable:
- case HostSingleton:
- case HostComponent:
+ case RootErrored:
{
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
+ // This render errored. Ignore any recoverable errors because we weren't actually
+ // able to recover. Instead, whatever the final errors were is the ones we log.
+ // This ensures that we only log the actual client side error if it's just a plain
+ // error thrown from a component on the server and the client.
+ workInProgressRootRecoverableErrors = null;
break;
}
- case OffscreenComponent:
+ case RootSuspended:
+ case RootCompleted:
{
- // TODO (Offscreen) Check: flags & RefStatic
- safelyDetachRef(finishedWork, finishedWork.return);
- var isHidden = finishedWork.memoizedState !== null;
-
- if (isHidden) ; else {
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- }
-
break;
}
default:
{
- recursivelyTraverseDisappearLayoutEffects(finishedWork);
- break;
+ throw new Error('Unknown root exit status.');
}
}
-}
-function recursivelyTraverseDisappearLayoutEffects(parentFiber) {
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
- var child = parentFiber.child;
+ if (shouldForceFlushFallbacksInDEV()) {
+ // We're inside an `act` scope. Commit immediately.
+ commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressRootDidIncludeRecursiveRenderUpdate, workInProgressDeferredLane);
+ } else {
+ if (includesOnlyRetries(lanes) && (alwaysThrottleRetries )) {
+ // This render only included retries, no updates. Throttle committing
+ // retries so that we don't show too many loading states too quickly.
+ var msUntilTimeout = globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now$1(); // Don't bother with a very short suspense time.
- while (child !== null) {
- disappearLayoutEffects(child);
- child = child.sibling;
- }
-}
+ if (msUntilTimeout > 10) {
+ markRootSuspended(root, lanes, workInProgressDeferredLane);
+ var nextLanes = getNextLanes(root, NoLanes);
-function reappearLayoutEffects(finishedRoot, current, finishedWork, // This function visits both newly finished work and nodes that were re-used
-// from a previously committed tree. We cannot check non-static flags if the
-// node was reused.
-includeWorkInProgressEffects) {
- // Turn on layout effects in a tree that previously disappeared.
- var flags = finishedWork.flags;
+ if (nextLanes !== NoLanes) {
+ // There's additional work we can do on this root. We might as well
+ // attempt to work on that while we're suspended.
+ return;
+ } // The render is suspended, it hasn't timed out, and there's no
+ // lower priority work to do. Instead of committing the fallback
+ // immediately, wait for more data to arrive.
+ // TODO: Combine retry throttling with Suspensey commits. Right now they
+ // run one after the other.
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Check flags & LayoutStatic
- commitHookLayoutEffects(finishedWork, Layout);
- break;
+ root.timeoutHandle = scheduleTimeout(commitRootWhenReady.bind(null, root, finishedWork, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressRootDidIncludeRecursiveRenderUpdate, lanes, workInProgressDeferredLane), msUntilTimeout);
+ return;
}
+ }
- case ClassComponent:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Check for LayoutStatic flag
+ commitRootWhenReady(root, finishedWork, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressRootDidIncludeRecursiveRenderUpdate, lanes, workInProgressDeferredLane);
+ }
+}
- var instance = finishedWork.stateNode;
+function commitRootWhenReady(root, finishedWork, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, lanes, spawnedLane) {
+ // TODO: Combine retry throttling with Suspensey commits. Right now they run
+ // one after the other.
+ if (includesOnlyNonUrgentLanes(lanes)) {
+ // Before committing, ask the renderer whether the host tree is ready.
+ // If it's not, we'll wait until it notifies us.
+ startSuspendingCommit(); // This will walk the completed fiber tree and attach listeners to all
+ // the suspensey resources. The renderer is responsible for accumulating
+ // all the load events. This all happens in a single synchronous
+ // transaction, so it track state in its own module scope.
- if (typeof instance.componentDidMount === 'function') {
- try {
- instance.componentDidMount();
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
- } // Commit any callbacks that would have fired while the component
- // was hidden.
+ accumulateSuspenseyCommit(finishedWork); // At the end, ask the renderer if it's ready to commit, or if we should
+ // suspend. If it's not ready, it will return a callback to subscribe to
+ // a ready event.
+ var schedulePendingCommit = waitForCommitToBeReady();
- var updateQueue = finishedWork.updateQueue;
+ if (schedulePendingCommit !== null) {
+ // NOTE: waitForCommitToBeReady returns a subscribe function so that we
+ // only allocate a function if the commit isn't ready yet. The other
+ // pattern would be to always pass a callback to waitForCommitToBeReady.
+ // Not yet ready to commit. Delay the commit until the renderer notifies
+ // us that it's ready. This will be canceled if we start work on the
+ // root again.
+ root.cancelPendingCommit = schedulePendingCommit(commitRoot.bind(null, root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate));
+ markRootSuspended(root, lanes, spawnedLane);
+ return;
+ }
+ } // Otherwise, commit immediately.
- if (updateQueue !== null) {
- commitHiddenCallbacks(updateQueue, instance);
- } // If this is newly finished work, check for setState callbacks
+ commitRoot(root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, spawnedLane);
+}
- if (includeWorkInProgressEffects && flags & Callback) {
- commitClassCallbacks(finishedWork);
- } // TODO: Check flags & RefStatic
+function isRenderConsistentWithExternalStores(finishedWork) {
+ // Search the rendered tree for external store reads, and check whether the
+ // stores were mutated in a concurrent event. Intentionally using an iterative
+ // loop instead of recursion so we can exit early.
+ var node = finishedWork;
+ while (true) {
+ if (node.flags & StoreConsistency) {
+ var updateQueue = node.updateQueue;
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
+ if (updateQueue !== null) {
+ var checks = updateQueue.stores;
+
+ if (checks !== null) {
+ for (var i = 0; i < checks.length; i++) {
+ var check = checks[i];
+ var getSnapshot = check.getSnapshot;
+ var renderedValue = check.value;
+
+ try {
+ if (!objectIs(getSnapshot(), renderedValue)) {
+ // Found an inconsistent store.
+ return false;
+ }
+ } catch (error) {
+ // If `getSnapshot` throws, return `false`. This will schedule
+ // a re-render, and the error will be rethrown during render.
+ return false;
+ }
+ }
+ }
}
- // Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
- // because this function only visits nodes that are inside an
- // Offscreen fiber.
- // case HostRoot: {
- // ...
- // }
+ }
- case HostHoistable:
- case HostSingleton:
- case HostComponent:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // Renderers may schedule work to be done after host components are mounted
- // (eg DOM renderer may schedule auto-focus for inputs and form controls).
- // These effects should only be committed when components are first mounted,
- // aka when there is no current/alternate.
+ var child = node.child;
- if (includeWorkInProgressEffects && current === null && flags & Update) {
- commitHostComponentMount(finishedWork);
- } // TODO: Check flags & Ref
+ if (node.subtreeFlags & StoreConsistency && child !== null) {
+ child.return = node;
+ node = child;
+ continue;
+ }
+ if (node === finishedWork) {
+ return true;
+ }
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
+ while (node.sibling === null) {
+ if (node.return === null || node.return === finishedWork) {
+ return true;
}
- case Profiler:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Figure out how Profiler updates should work with Offscreen
+ node = node.return;
+ }
- if (includeWorkInProgressEffects && flags & Update) {
- commitProfilerUpdate(finishedWork, current);
- }
+ node.sibling.return = node.return;
+ node = node.sibling;
+ } // Flow doesn't know this is unreachable, but eslint does
+ // eslint-disable-next-line no-unreachable
- break;
- }
- case SuspenseComponent:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects); // TODO: Figure out how Suspense hydration callbacks should work
- // with Offscreen.
+ return true;
+} // The extra indirections around markRootUpdated and markRootSuspended is
+// needed to avoid a circular dependency between this module and
+// ReactFiberLane. There's probably a better way to split up these modules and
+// avoid this problem. Perhaps all the root-marking functions should move into
+// the work loop.
- if (includeWorkInProgressEffects && flags & Update) {
- commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
- }
- break;
- }
+function markRootUpdated(root, updatedLanes) {
+ markRootUpdated$1(root, updatedLanes);
- case OffscreenComponent:
- {
- var offscreenState = finishedWork.memoizedState;
- var isHidden = offscreenState !== null;
+ {
+ // Check for recursive updates
+ if (executionContext & RenderContext) {
+ workInProgressRootDidIncludeRecursiveRenderUpdate = true;
+ } else if (executionContext & CommitContext) {
+ didIncludeCommitPhaseUpdate = true;
+ }
- if (isHidden) ; else {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
- } // TODO: Check flags & Ref
+ throwIfInfiniteUpdateLoopDetected();
+ }
+}
+function markRootPinged(root, pingedLanes) {
+ markRootPinged$1(root, pingedLanes);
- safelyAttachRef(finishedWork, finishedWork.return);
- break;
- }
+ {
+ // Check for recursive pings. Pings are conceptually different from updates in
+ // other contexts but we call it an "update" in this context because
+ // repeatedly pinging a suspended render can cause a recursive render loop.
+ // The relevant property is that it can result in a new render attempt
+ // being scheduled.
+ if (executionContext & RenderContext) {
+ workInProgressRootDidIncludeRecursiveRenderUpdate = true;
+ } else if (executionContext & CommitContext) {
+ didIncludeCommitPhaseUpdate = true;
+ }
- default:
- {
- recursivelyTraverseReappearLayoutEffects(finishedRoot, finishedWork, includeWorkInProgressEffects);
- break;
- }
+ throwIfInfiniteUpdateLoopDetected();
}
}
-function recursivelyTraverseReappearLayoutEffects(finishedRoot, parentFiber, includeWorkInProgressEffects) {
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- var childShouldIncludeWorkInProgressEffects = includeWorkInProgressEffects && (parentFiber.subtreeFlags & LayoutMask) !== NoFlags$1; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
+function markRootSuspended(root, suspendedLanes, spawnedLane) {
+ // When suspending, we should always exclude lanes that were pinged or (more
+ // rarely, since we try to avoid it) updated during the render phase.
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootInterleavedUpdatedLanes);
- var prevDebugFiber = getCurrentFiber();
- var child = parentFiber.child;
+ markRootSuspended$1(root, suspendedLanes, spawnedLane);
+} // This is the entry point for synchronous tasks that don't go
+// through Scheduler
- while (child !== null) {
- var current = child.alternate;
- reappearLayoutEffects(finishedRoot, current, child, childShouldIncludeWorkInProgressEffects);
- child = child.sibling;
+
+function performSyncWorkOnRoot(root, lanes) {
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
+ throw new Error('Should not already be working.');
}
- setCurrentFiber(prevDebugFiber);
-}
+ var didFlushPassiveEffects = flushPassiveEffects();
-function commitHookPassiveMountEffects(finishedWork, hookFlags) {
- if (shouldProfile(finishedWork)) {
- startPassiveEffectTimer();
+ if (didFlushPassiveEffects) {
+ // If passive effects were flushed, exit to the outer work loop in the root
+ // scheduler, so we can recompute the priority.
+ // TODO: We don't actually need this `ensureRootIsScheduled` call because
+ // this path is only reachable if the root is already part of the schedule.
+ // I'm including it only for consistency with the other exit points from
+ // this function. Can address in a subsequent refactor.
+ ensureRootIsScheduled(root);
+ return null;
+ }
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
- }
+ {
+ syncNestedUpdateFlag();
+ }
- recordPassiveEffectDuration(finishedWork);
- } else {
- try {
- commitHookEffectListMount(hookFlags, finishedWork);
- } catch (error) {
- captureCommitPhaseError(finishedWork, finishedWork.return, error);
+ var exitStatus = renderRootSync(root, lanes);
+
+ if (exitStatus === RootErrored) {
+ // If something threw an error, try rendering one more time. We'll render
+ // synchronously to block concurrent data mutations, and we'll includes
+ // all pending updates are included. If it still fails after the second
+ // attempt, we'll give up and commit the resulting tree.
+ var originallyAttemptedLanes = lanes;
+ var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root, originallyAttemptedLanes);
+
+ if (errorRetryLanes !== NoLanes) {
+ lanes = errorRetryLanes;
+ exitStatus = recoverFromConcurrentError(root, originallyAttemptedLanes, errorRetryLanes);
}
}
-}
-function commitOffscreenPassiveMountEffects(current, finishedWork, instance) {
- {
- var previousCache = null;
+ if (exitStatus === RootFatalErrored) {
+ prepareFreshStack(root, NoLanes);
+ markRootSuspended(root, lanes, NoLane);
+ ensureRootIsScheduled(root);
+ return null;
+ }
- if (current !== null && current.memoizedState !== null && current.memoizedState.cachePool !== null) {
- previousCache = current.memoizedState.cachePool.pool;
- }
+ if (exitStatus === RootDidNotComplete) {
+ // The render unwound without completing the tree. This happens in special
+ // cases where need to exit the current render without producing a
+ // consistent tree or committing.
+ markRootSuspended(root, lanes, workInProgressDeferredLane);
+ ensureRootIsScheduled(root);
+ return null;
+ } // We now have a consistent tree. Because this is a sync render, we
+ // will commit it even if something suspended.
- var nextCache = null;
- if (finishedWork.memoizedState !== null && finishedWork.memoizedState.cachePool !== null) {
- nextCache = finishedWork.memoizedState.cachePool.pool;
- } // Retain/release the cache used for pending (suspended) nodes.
- // Note that this is only reached in the non-suspended/visible case:
- // when the content is suspended/hidden, the retain/release occurs
- // via the parent Suspense component (see case above).
+ var finishedWork = root.current.alternate;
+ root.finishedWork = finishedWork;
+ root.finishedLanes = lanes;
+ commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions, workInProgressRootDidIncludeRecursiveRenderUpdate, workInProgressDeferredLane); // Before exiting, make sure there's a callback scheduled for the next
+ // pending level.
+ ensureRootIsScheduled(root);
+ return null;
+}
+function flushRoot(root, lanes) {
+ if (lanes !== NoLanes) {
+ upgradePendingLanesToSync(root, lanes);
+ ensureRootIsScheduled(root);
- if (nextCache !== previousCache) {
- if (nextCache != null) {
- retainCache(nextCache);
- }
+ if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
+ resetRenderTimer(); // TODO: For historical reasons this flushes all sync work across all
+ // roots. It shouldn't really matter either way, but we could change this
+ // to only flush the given root.
- if (previousCache != null) {
- releaseCache(previousCache);
- }
+ flushSyncWorkOnAllRoots();
}
}
}
-
-function commitCachePassiveMountEffect(current, finishedWork) {
+function getExecutionContext() {
+ return executionContext;
+}
+function batchedUpdates(fn, a) {
{
- var previousCache = null;
-
- if (finishedWork.alternate !== null) {
- previousCache = finishedWork.alternate.memoizedState.cache;
- }
-
- var nextCache = finishedWork.memoizedState.cache; // Retain/release the cache. In theory the cache component
- // could be "borrowing" a cache instance owned by some parent,
- // in which case we could avoid retaining/releasing. But it
- // is non-trivial to determine when that is the case, so we
- // always retain/release.
-
- if (nextCache !== previousCache) {
- retainCache(nextCache);
+ // batchedUpdates is a no-op now, but there's still some internal react-dom
+ // code calling it, that we can't remove until we remove legacy mode.
+ return fn(a);
+ }
+}
+// Returns whether the the call was during a render or not
- if (previousCache != null) {
- releaseCache(previousCache);
- }
- }
+function flushSyncWork$1() {
+ if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
+ flushSyncWorkOnAllRoots();
+ return false;
}
+
+ return true;
+}
+function isAlreadyRendering() {
+ // Used by the renderer to print a warning if certain APIs are called from
+ // the wrong context.
+ return (executionContext & (RenderContext | CommitContext)) !== NoContext;
}
+function isInvalidExecutionContextForEventFunction() {
+ // Used to throw if certain APIs are called from the wrong context.
+ return (executionContext & RenderContext) !== NoContext;
+} // This is called by the HiddenContext module when we enter or leave a
+// hidden subtree. The stack logic is managed there because that's the only
+// place that ever modifies it. Which module it lives in doesn't matter for
+// performance because this function will get inlined regardless
-function commitPassiveMountEffects(root, finishedWork, committedLanes, committedTransitions) {
- setCurrentFiber(finishedWork);
- commitPassiveMountOnFiber(root, finishedWork, committedLanes, committedTransitions);
- resetCurrentFiber();
+function setEntangledRenderLanes(newEntangledRenderLanes) {
+ entangledRenderLanes = newEntangledRenderLanes;
+}
+function getEntangledRenderLanes() {
+ return entangledRenderLanes;
}
-function recursivelyTraversePassiveMountEffects(root, parentFiber, committedLanes, committedTransitions) {
- var prevDebugFiber = getCurrentFiber();
+function resetWorkInProgressStack() {
+ if (workInProgress === null) return;
+ var interruptedWork;
- if (parentFiber.subtreeFlags & PassiveMask) {
- var child = parentFiber.child;
+ if (workInProgressSuspendedReason === NotSuspended) {
+ // Normal case. Work-in-progress hasn't started yet. Unwind all
+ // its parents.
+ interruptedWork = workInProgress.return;
+ } else {
+ // Work-in-progress is in suspended state. Reset the work loop and unwind
+ // both the suspended fiber and all its parents.
+ resetSuspendedWorkLoopOnUnwind(workInProgress);
+ interruptedWork = workInProgress;
+ }
- while (child !== null) {
- setCurrentFiber(child);
- commitPassiveMountOnFiber(root, child, committedLanes, committedTransitions);
- child = child.sibling;
- }
+ while (interruptedWork !== null) {
+ var current = interruptedWork.alternate;
+ unwindInterruptedWork(current, interruptedWork);
+ interruptedWork = interruptedWork.return;
}
- setCurrentFiber(prevDebugFiber);
+ workInProgress = null;
}
-function commitPassiveMountOnFiber(finishedRoot, finishedWork, committedLanes, committedTransitions) {
- // When updating this function, also update reconnectPassiveEffects, which does
- // most of the same things when an offscreen tree goes from hidden -> visible,
- // or when toggling effects inside a hidden tree.
- var flags = finishedWork.flags;
-
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
-
- if (flags & Passive$1) {
- commitHookPassiveMountEffects(finishedWork, Passive | HasEffect);
- }
-
- break;
- }
-
- case HostRoot:
- {
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
-
- if (flags & Passive$1) {
- {
- var previousCache = null;
-
- if (finishedWork.alternate !== null) {
- previousCache = finishedWork.alternate.memoizedState.cache;
- }
-
- var nextCache = finishedWork.memoizedState.cache; // Retain/release the root cache.
- // Note that on initial mount, previousCache and nextCache will be the same
- // and this retain won't occur. To counter this, we instead retain the HostRoot's
- // initial cache when creating the root itself (see createFiberRoot() in
- // ReactFiberRoot.js). Subsequent updates that change the cache are reflected
- // here, such that previous/next caches are retained correctly.
+function prepareFreshStack(root, lanes) {
+ root.finishedWork = null;
+ root.finishedLanes = NoLanes;
+ var timeoutHandle = root.timeoutHandle;
- if (nextCache !== previousCache) {
- retainCache(nextCache);
+ if (timeoutHandle !== noTimeout) {
+ // The root previous suspended and scheduled a timeout to commit a fallback
+ // state. Now that we have additional work, cancel the timeout.
+ root.timeoutHandle = noTimeout; // $FlowFixMe[incompatible-call] Complains noTimeout is not a TimeoutID, despite the check above
- if (previousCache != null) {
- releaseCache(previousCache);
- }
- }
- }
- }
+ cancelTimeout(timeoutHandle);
+ }
- break;
- }
+ var cancelPendingCommit = root.cancelPendingCommit;
- case LegacyHiddenComponent:
- {
+ if (cancelPendingCommit !== null) {
+ root.cancelPendingCommit = null;
+ cancelPendingCommit();
+ }
- break;
- }
+ resetWorkInProgressStack();
+ workInProgressRoot = root;
+ var rootWorkInProgress = createWorkInProgress(root.current, null);
+ workInProgress = rootWorkInProgress;
+ workInProgressRootRenderLanes = lanes;
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ workInProgressRootDidAttachPingListener = false;
+ workInProgressRootExitStatus = RootInProgress;
+ workInProgressRootSkippedLanes = NoLanes;
+ workInProgressRootInterleavedUpdatedLanes = NoLanes;
+ workInProgressRootPingedLanes = NoLanes;
+ workInProgressDeferredLane = NoLane;
+ workInProgressRootConcurrentErrors = null;
+ workInProgressRootRecoverableErrors = null;
+ workInProgressRootDidIncludeRecursiveRenderUpdate = false; // Get the lanes that are entangled with whatever we're about to render. We
+ // track these separately so we can distinguish the priority of the render
+ // task from the priority of the lanes it is entangled with. For example, a
+ // transition may not be allowed to finish unless it includes the Sync lane,
+ // which is currently suspended. We should be able to render the Transition
+ // and Sync lane in the same batch, but at Transition priority, because the
+ // Sync lane already suspended.
- case OffscreenComponent:
- {
- // TODO: Pass `current` as argument to this function
- var _instance3 = finishedWork.stateNode;
- var nextState = finishedWork.memoizedState;
- var isHidden = nextState !== null;
+ entangledRenderLanes = getEntangledLanes(root, lanes);
+ finishQueueingConcurrentUpdates();
- if (isHidden) {
- if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- } else {
- {
- // The effects are currently disconnected. Since the tree is hidden,
- // don't connect them. This also applies to the initial render.
- {
- // "Atomic" effects are ones that need to fire on every commit,
- // even during pre-rendering. An example is updating the reference
- // count on cache instances.
- recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
- }
- }
- }
- } else {
- // Tree is visible
- if (_instance3._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- } else {
- // The effects are currently disconnected. Reconnect them, while also
- // firing effects inside newly mounted trees. This also applies to
- // the initial render.
- _instance3._visibility |= OffscreenPassiveEffectsConnected;
- var includeWorkInProgressEffects = (finishedWork.subtreeFlags & PassiveMask) !== NoFlags$1;
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
- }
- }
+ {
+ ReactStrictModeWarnings.discardPendingWarnings();
+ }
- if (flags & Passive$1) {
- var _current = finishedWork.alternate;
- commitOffscreenPassiveMountEffects(_current, finishedWork);
- }
+ return rootWorkInProgress;
+}
- break;
- }
+function resetSuspendedWorkLoopOnUnwind(fiber) {
+ // Reset module-level state that was set during the render phase.
+ resetContextDependencies();
+ resetHooksOnUnwind(fiber);
+ resetChildReconcilerOnUnwind();
+}
- case CacheComponent:
- {
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
+function handleThrow(root, thrownValue) {
+ // A component threw an exception. Usually this is because it suspended, but
+ // it also includes regular program errors.
+ //
+ // We're either going to unwind the stack to show a Suspense or error
+ // boundary, or we're going to replay the component again. Like after a
+ // promise resolves.
+ //
+ // Until we decide whether we're going to unwind or replay, we should preserve
+ // the current state of the work loop without resetting anything.
+ //
+ // If we do decide to unwind the stack, module-level variables will be reset
+ // in resetSuspendedWorkLoopOnUnwind.
+ // These should be reset immediately because they're only supposed to be set
+ // when React is executing user code.
+ resetHooksAfterThrow();
+ resetCurrentFiber();
- if (flags & Passive$1) {
- // TODO: Pass `current` as argument to this function
- var _current2 = finishedWork.alternate;
- commitCachePassiveMountEffect(_current2, finishedWork);
- }
+ {
+ setCurrentOwner(null);
+ }
- break;
- }
+ if (thrownValue === SuspenseException) {
+ // This is a special type of exception used for Suspense. For historical
+ // reasons, the rest of the Suspense implementation expects the thrown value
+ // to be a thenable, because before `use` existed that was the (unstable)
+ // API for suspending. This implementation detail can change later, once we
+ // deprecate the old API in favor of `use`.
+ thrownValue = getSuspendedThenable();
+ workInProgressSuspendedReason = shouldRemainOnPreviousScreen() && // Check if there are other pending updates that might possibly unblock this
+ // component from suspending. This mirrors the check in
+ // renderDidSuspendDelayIfPossible. We should attempt to unify them somehow.
+ // TODO: Consider unwinding immediately, using the
+ // SuspendedOnHydration mechanism.
+ !includesNonIdleWork(workInProgressRootSkippedLanes) && !includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes) ? // Suspend work loop until data resolves
+ SuspendedOnData : // Don't suspend work loop, except to check if the data has
+ // immediately resolved (i.e. in a microtask). Otherwise, trigger the
+ // nearest Suspense fallback.
+ SuspendedOnImmediate;
+ } else if (thrownValue === SuspenseyCommitException) {
+ thrownValue = getSuspendedThenable();
+ workInProgressSuspendedReason = SuspendedOnInstance;
+ } else if (thrownValue === SelectiveHydrationException) {
+ // An update flowed into a dehydrated boundary. Before we can apply the
+ // update, we need to finish hydrating. Interrupt the work-in-progress
+ // render so we can restart at the hydration lane.
+ //
+ // The ideal implementation would be able to switch contexts without
+ // unwinding the current stack.
+ //
+ // We could name this something more general but as of now it's the only
+ // case where we think this should happen.
+ workInProgressSuspendedReason = SuspendedOnHydration;
+ } else {
+ // This is a regular error.
+ var isWakeable = thrownValue !== null && typeof thrownValue === 'object' && typeof thrownValue.then === 'function';
+ workInProgressSuspendedReason = isWakeable ? // A wakeable object was thrown by a legacy Suspense implementation.
+ // This has slightly different behavior than suspending with `use`.
+ SuspendedOnDeprecatedThrowPromise : // This is a regular error. If something earlier in the component already
+ // suspended, we must clear the thenable state to unblock the work loop.
+ SuspendedOnError;
+ }
- case TracingMarkerComponent:
+ workInProgressThrownValue = thrownValue;
+ var erroredWork = workInProgress;
- default:
- {
- recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork, committedLanes, committedTransitions);
- break;
- }
+ if (erroredWork === null) {
+ // This is a fatal error
+ workInProgressRootExitStatus = RootFatalErrored;
+ logUncaughtError(root, createCapturedValueAtFiber(thrownValue, root.current));
+ return;
}
-}
-function recursivelyTraverseReconnectPassiveEffects(finishedRoot, parentFiber, committedLanes, committedTransitions, includeWorkInProgressEffects) {
- // This function visits both newly finished work and nodes that were re-used
- // from a previously committed tree. We cannot check non-static flags if the
- // node was reused.
- var childShouldIncludeWorkInProgressEffects = includeWorkInProgressEffects && (parentFiber.subtreeFlags & PassiveMask) !== NoFlags$1; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
+ if (erroredWork.mode & ProfileMode) {
+ // Record the time spent rendering before an error was thrown. This
+ // avoids inaccurate Profiler durations in the case of a
+ // suspended render.
+ stopProfilerTimerIfRunningAndRecordDelta(erroredWork, true);
+ }
- var prevDebugFiber = getCurrentFiber();
- var child = parentFiber.child;
+ {
+ markComponentRenderStopped();
- while (child !== null) {
- reconnectPassiveEffects(finishedRoot, child, committedLanes, committedTransitions, childShouldIncludeWorkInProgressEffects);
- child = child.sibling;
- }
+ switch (workInProgressSuspendedReason) {
+ case SuspendedOnError:
+ {
+ markComponentErrored(erroredWork, thrownValue, workInProgressRootRenderLanes);
+ break;
+ }
- setCurrentFiber(prevDebugFiber);
+ case SuspendedOnData:
+ case SuspendedOnImmediate:
+ case SuspendedOnDeprecatedThrowPromise:
+ case SuspendedAndReadyToContinue:
+ {
+ var wakeable = thrownValue;
+ markComponentSuspended(erroredWork, wakeable, workInProgressRootRenderLanes);
+ break;
+ }
+ }
+ }
}
-function reconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, // This function visits both newly finished work and nodes that were re-used
-// from a previously committed tree. We cannot check non-static flags if the
-// node was reused.
-includeWorkInProgressEffects) {
- var flags = finishedWork.flags;
+function shouldRemainOnPreviousScreen() {
+ // This is asking whether it's better to suspend the transition and remain
+ // on the previous screen, versus showing a fallback as soon as possible. It
+ // takes into account both the priority of render and also whether showing a
+ // fallback would produce a desirable user experience.
+ var handler = getSuspenseHandler();
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects); // TODO: Check for PassiveStatic flag
+ if (handler === null) {
+ // There's no Suspense boundary that can provide a fallback. We have no
+ // choice but to remain on the previous screen.
+ // NOTE: We do this even for sync updates, for lack of any better option. In
+ // the future, we may change how we handle this, like by putting the whole
+ // root into a "detached" mode.
+ return true;
+ } // TODO: Once `use` has fully replaced the `throw promise` pattern, we should
+ // be able to remove the equivalent check in finishConcurrentRender, and rely
+ // just on this one.
- commitHookPassiveMountEffects(finishedWork, Passive);
- break;
- }
- // Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
- // because this function only visits nodes that are inside an
- // Offscreen fiber.
- // case HostRoot: {
- // ...
- // }
- case LegacyHiddenComponent:
- {
+ if (includesOnlyTransitions(workInProgressRootRenderLanes)) {
+ if (getShellBoundary() === null) {
+ // We're rendering inside the "shell" of the app. Activating the nearest
+ // fallback would cause visible content to disappear. It's better to
+ // suspend the transition and remain on the previous screen.
+ return true;
+ } else {
+ // We're rendering content that wasn't part of the previous screen.
+ // Rather than block the transition, it's better to show a fallback as
+ // soon as possible. The appearance of any nested fallbacks will be
+ // throttled to avoid jank.
+ return false;
+ }
+ }
- break;
- }
+ if (includesOnlyRetries(workInProgressRootRenderLanes) || // In this context, an OffscreenLane counts as a Retry
+ // TODO: It's become increasingly clear that Retries and Offscreen are
+ // deeply connected. They probably can be unified further.
+ includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)) {
+ // During a retry, we can suspend rendering if the nearest Suspense boundary
+ // is the boundary of the "shell", because we're guaranteed not to block
+ // any new content from appearing.
+ //
+ // The reason we must check if this is a retry is because it guarantees
+ // that suspending the work loop won't block an actual update, because
+ // retries don't "update" anything; they fill in fallbacks that were left
+ // behind by a previous transition.
+ return handler === getShellBoundary();
+ } // For all other Lanes besides Transitions and Retries, we should not wait
+ // for the data to load.
- case OffscreenComponent:
- {
- var _instance4 = finishedWork.stateNode;
- var nextState = finishedWork.memoizedState;
- var isHidden = nextState !== null;
- if (isHidden) {
- if (_instance4._visibility & OffscreenPassiveEffectsConnected) {
- // The effects are currently connected. Update them.
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
- } else {
- {
- // The effects are currently disconnected. Since the tree is hidden,
- // don't connect them. This also applies to the initial render.
- {
- // "Atomic" effects are ones that need to fire on every commit,
- // even during pre-rendering. An example is updating the reference
- // count on cache instances.
- recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
- }
- }
- }
- } else {
- // Tree is visible
- // Since we're already inside a reconnecting tree, it doesn't matter
- // whether the effects are currently connected. In either case, we'll
- // continue traversing the tree and firing all the effects.
- //
- // We do need to set the "connected" flag on the instance, though.
- _instance4._visibility |= OffscreenPassiveEffectsConnected;
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
- }
+ return false;
+}
- if (includeWorkInProgressEffects && flags & Passive$1) {
- // TODO: Pass `current` as argument to this function
- var _current3 = finishedWork.alternate;
- commitOffscreenPassiveMountEffects(_current3, finishedWork);
- }
+function pushDispatcher(container) {
+ var prevDispatcher = ReactSharedInternals.H;
+ ReactSharedInternals.H = ContextOnlyDispatcher;
- break;
- }
+ if (prevDispatcher === null) {
+ // The React isomorphic package does not include a default dispatcher.
+ // Instead the first renderer will lazily attach one, in order to give
+ // nicer error messages.
+ return ContextOnlyDispatcher;
+ } else {
+ return prevDispatcher;
+ }
+}
- case CacheComponent:
- {
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
+function popDispatcher(prevDispatcher) {
+ ReactSharedInternals.H = prevDispatcher;
+}
- if (includeWorkInProgressEffects && flags & Passive$1) {
- // TODO: Pass `current` as argument to this function
- var _current4 = finishedWork.alternate;
- commitCachePassiveMountEffect(_current4, finishedWork);
- }
+function pushAsyncDispatcher() {
+ {
+ var prevAsyncDispatcher = ReactSharedInternals.A;
+ ReactSharedInternals.A = DefaultAsyncDispatcher;
+ return prevAsyncDispatcher;
+ }
+}
- break;
- }
+function popAsyncDispatcher(prevAsyncDispatcher) {
+ {
+ ReactSharedInternals.A = prevAsyncDispatcher;
+ }
+}
- case TracingMarkerComponent:
+function markCommitTimeOfFallback() {
+ globalMostRecentFallbackTime = now$1();
+}
+function markSkippedUpdateLanes(lane) {
+ workInProgressRootSkippedLanes = mergeLanes(lane, workInProgressRootSkippedLanes);
+}
+function renderDidSuspend() {
+ if (workInProgressRootExitStatus === RootInProgress) {
+ workInProgressRootExitStatus = RootSuspended;
+ }
+}
+function renderDidSuspendDelayIfPossible() {
+ workInProgressRootExitStatus = RootSuspendedWithDelay; // Check if there are updates that we skipped tree that might have unblocked
+ // this render.
- default:
- {
- recursivelyTraverseReconnectPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions, includeWorkInProgressEffects);
- break;
- }
+ if ((includesNonIdleWork(workInProgressRootSkippedLanes) || includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) && workInProgressRoot !== null) {
+ // Mark the current render as suspended so that we switch to working on
+ // the updates that were skipped. Usually we only suspend at the end of
+ // the render phase.
+ // TODO: We should probably always mark the root as suspended immediately
+ // (inside this function), since by suspending at the end of the render
+ // phase introduces a potential mistake where we suspend lanes that were
+ // pinged or updated while we were rendering.
+ // TODO: Consider unwinding immediately, using the
+ // SuspendedOnHydration mechanism.
+ markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes, workInProgressDeferredLane);
+ }
+}
+function renderDidError() {
+ if (workInProgressRootExitStatus !== RootSuspendedWithDelay) {
+ workInProgressRootExitStatus = RootErrored;
}
}
+function queueConcurrentError(error) {
+ if (workInProgressRootConcurrentErrors === null) {
+ workInProgressRootConcurrentErrors = [error];
+ } else {
+ workInProgressRootConcurrentErrors.push(error);
+ }
+} // Called during render to determine if anything has suspended.
+// Returns false if we're not sure.
-function recursivelyTraverseAtomicPassiveEffects(finishedRoot, parentFiber, committedLanes, committedTransitions) {
- // "Atomic" effects are ones that need to fire on every commit, even during
- // pre-rendering. We call this function when traversing a hidden tree whose
- // regular effects are currently disconnected.
- var prevDebugFiber = getCurrentFiber(); // TODO: Add special flag for atomic effects
+function renderHasNotSuspendedYet() {
+ // If something errored or completed, we can't really be sure,
+ // so those are false.
+ return workInProgressRootExitStatus === RootInProgress;
+} // TODO: Over time, this function and renderRootConcurrent have become more
+// and more similar. Not sure it makes sense to maintain forked paths. Consider
+// unifying them again.
+
+function renderRootSync(root, lanes) {
+ var prevExecutionContext = executionContext;
+ executionContext |= RenderContext;
+ var prevDispatcher = pushDispatcher();
+ var prevAsyncDispatcher = pushAsyncDispatcher(); // If the root or lanes have changed, throw out the existing stack
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
+
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
+ {
+ if (isDevToolsPresent) {
+ var memoizedUpdaters = root.memoizedUpdaters;
+
+ if (memoizedUpdaters.size > 0) {
+ restorePendingUpdaters(root, workInProgressRootRenderLanes);
+ memoizedUpdaters.clear();
+ } // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
+ // If we bailout on this work, we'll move them back (like above).
+ // It's important to move them now in case the work spawns more work at the same priority with different updaters.
+ // That way we can keep the current update and future updates separate.
- if (parentFiber.subtreeFlags & PassiveMask) {
- var child = parentFiber.child;
- while (child !== null) {
- setCurrentFiber(child);
- commitAtomicPassiveEffects(finishedRoot, child);
- child = child.sibling;
+ movePendingFibersToMemoized(root, lanes);
+ }
}
+
+ workInProgressTransitions = getTransitionsForLanes();
+ prepareFreshStack(root, lanes);
}
- setCurrentFiber(prevDebugFiber);
-}
+ {
+ markRenderStarted(lanes);
+ }
-function commitAtomicPassiveEffects(finishedRoot, finishedWork, committedLanes, committedTransitions) {
- // "Atomic" effects are ones that need to fire on every commit, even during
- // pre-rendering. We call this function when traversing a hidden tree whose
- // regular effects are currently disconnected.
- var flags = finishedWork.flags;
+ var didSuspendInShell = false;
- switch (finishedWork.tag) {
- case OffscreenComponent:
- {
- recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
+ outer: do {
+ try {
+ if (workInProgressSuspendedReason !== NotSuspended && workInProgress !== null) {
+ // The work loop is suspended. During a synchronous render, we don't
+ // yield to the main thread. Immediately unwind the stack. This will
+ // trigger either a fallback or an error boundary.
+ // TODO: For discrete and "default" updates (anything that's not
+ // flushSync), we want to wait for the microtasks the flush before
+ // unwinding. Will probably implement this using renderRootConcurrent,
+ // or merge renderRootSync and renderRootConcurrent into the same
+ // function and fork the behavior some other way.
+ var unitOfWork = workInProgress;
+ var thrownValue = workInProgressThrownValue;
- if (flags & Passive$1) {
- // TODO: Pass `current` as argument to this function
- var current = finishedWork.alternate;
- commitOffscreenPassiveMountEffects(current, finishedWork);
- }
+ switch (workInProgressSuspendedReason) {
+ case SuspendedOnHydration:
+ {
+ // Selective hydration. An update flowed into a dehydrated tree.
+ // Interrupt the current render so the work loop can switch to the
+ // hydration lane.
+ resetWorkInProgressStack();
+ workInProgressRootExitStatus = RootDidNotComplete;
+ break outer;
+ }
- break;
- }
+ case SuspendedOnImmediate:
+ case SuspendedOnData:
+ {
+ if (!didSuspendInShell && getSuspenseHandler() === null) {
+ didSuspendInShell = true;
+ } // Intentional fallthrough
- case CacheComponent:
- {
- recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
+ }
- if (flags & Passive$1) {
- // TODO: Pass `current` as argument to this function
- var _current5 = finishedWork.alternate;
- commitCachePassiveMountEffect(_current5, finishedWork);
+ default:
+ {
+ // Unwind then continue with the normal work loop.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
+ break;
+ }
}
-
- break;
}
- default:
- {
- recursivelyTraverseAtomicPassiveEffects(finishedRoot, finishedWork);
- break;
- }
- }
-}
+ workLoopSync();
+ break;
+ } catch (thrownValue) {
+ handleThrow(root, thrownValue);
+ }
+ } while (true); // Check if something suspended in the shell. We use this to detect an
+ // infinite ping loop caused by an uncached promise.
+ //
+ // Only increment this counter once per synchronous render attempt across the
+ // whole tree. Even if there are many sibling components that suspend, this
+ // counter only gets incremented once.
-function commitPassiveUnmountEffects(finishedWork) {
- setCurrentFiber(finishedWork);
- commitPassiveUnmountOnFiber(finishedWork);
- resetCurrentFiber();
-} // If we're inside a brand new tree, or a tree that was already visible, then we
-// should only suspend host components that have a ShouldSuspendCommit flag.
-// Components without it haven't changed since the last commit, so we can skip
-// over those.
-//
-// When we enter a tree that is being revealed (going from hidden -> visible),
-// we need to suspend _any_ component that _may_ suspend. Even if they're
-// already in the "current" tree. Because their visibility has changed, the
-// browser may not have prerendered them yet. So we check the MaySuspendCommit
-// flag instead.
-var suspenseyCommitFlag = ShouldSuspendCommit;
-function accumulateSuspenseyCommit(finishedWork) {
- accumulateSuspenseyCommitOnFiber(finishedWork);
-}
+ if (didSuspendInShell) {
+ root.shellSuspendCounter++;
+ }
-function recursivelyAccumulateSuspenseyCommit(parentFiber) {
- if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
- var child = parentFiber.child;
+ resetContextDependencies();
+ executionContext = prevExecutionContext;
+ popDispatcher(prevDispatcher);
+ popAsyncDispatcher(prevAsyncDispatcher);
- while (child !== null) {
- accumulateSuspenseyCommitOnFiber(child);
- child = child.sibling;
- }
+ if (workInProgress !== null) {
+ // This is a sync render, so we should have finished the whole tree.
+ throw new Error('Cannot commit an incomplete root. This error is likely caused by a ' + 'bug in React. Please file an issue.');
}
-}
-function accumulateSuspenseyCommitOnFiber(fiber) {
- switch (fiber.tag) {
- case HostHoistable:
- {
- recursivelyAccumulateSuspenseyCommit(fiber);
+ {
+ markRenderStopped();
+ } // Set this to null to indicate there's no in-progress render.
- if (fiber.flags & suspenseyCommitFlag) {
- if (fiber.memoizedState !== null) {
- suspendResource( // This should always be set by visiting HostRoot first
- currentHoistableRoot, fiber.memoizedState, fiber.memoizedProps);
- }
- }
- break;
- }
+ workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete.
- case HostComponent:
- {
- recursivelyAccumulateSuspenseyCommit(fiber);
+ finishQueueingConcurrentUpdates();
+ return workInProgressRootExitStatus;
+} // The work loop is an extremely hot path. Tell Closure not to inline it.
- break;
- }
+/** @noinline */
- case HostRoot:
- case HostPortal:
- {
- {
- var previousHoistableRoot = currentHoistableRoot;
- var container = fiber.stateNode.containerInfo;
- currentHoistableRoot = getHoistableRoot(container);
- recursivelyAccumulateSuspenseyCommit(fiber);
- currentHoistableRoot = previousHoistableRoot;
- }
- break;
- }
+function workLoopSync() {
+ // Perform work without checking if we need to yield between fiber.
+ while (workInProgress !== null) {
+ performUnitOfWork(workInProgress);
+ }
+}
- case OffscreenComponent:
- {
- var isHidden = fiber.memoizedState !== null;
+function renderRootConcurrent(root, lanes) {
+ var prevExecutionContext = executionContext;
+ executionContext |= RenderContext;
+ var prevDispatcher = pushDispatcher();
+ var prevAsyncDispatcher = pushAsyncDispatcher(); // If the root or lanes have changed, throw out the existing stack
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
- if (isHidden) ; else {
- var current = fiber.alternate;
- var wasHidden = current !== null && current.memoizedState !== null;
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
+ {
+ if (isDevToolsPresent) {
+ var memoizedUpdaters = root.memoizedUpdaters;
- if (wasHidden) {
- // This tree is being revealed. Visit all newly visible suspensey
- // instances, even if they're in the current tree.
- var prevFlags = suspenseyCommitFlag;
- suspenseyCommitFlag = MaySuspendCommit;
- recursivelyAccumulateSuspenseyCommit(fiber);
- suspenseyCommitFlag = prevFlags;
- } else {
- recursivelyAccumulateSuspenseyCommit(fiber);
- }
- }
+ if (memoizedUpdaters.size > 0) {
+ restorePendingUpdaters(root, workInProgressRootRenderLanes);
+ memoizedUpdaters.clear();
+ } // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
+ // If we bailout on this work, we'll move them back (like above).
+ // It's important to move them now in case the work spawns more work at the same priority with different updaters.
+ // That way we can keep the current update and future updates separate.
- break;
- }
- default:
- {
- recursivelyAccumulateSuspenseyCommit(fiber);
+ movePendingFibersToMemoized(root, lanes);
}
+ }
+
+ workInProgressTransitions = getTransitionsForLanes();
+ resetRenderTimer();
+ prepareFreshStack(root, lanes);
}
-}
-function detachAlternateSiblings(parentFiber) {
- // A fiber was deleted from this parent fiber, but it's still part of the
- // previous (alternate) parent fiber's list of children. Because children
- // are a linked list, an earlier sibling that's still alive will be
- // connected to the deleted fiber via its `alternate`:
- //
- // live fiber --alternate--> previous live fiber --sibling--> deleted
- // fiber
- //
- // We can't disconnect `alternate` on nodes that haven't been deleted yet,
- // but we can disconnect the `sibling` and `child` pointers.
- var previousFiber = parentFiber.alternate;
+ {
+ markRenderStarted(lanes);
+ }
+
+ outer: do {
+ try {
+ if (workInProgressSuspendedReason !== NotSuspended && workInProgress !== null) {
+ // The work loop is suspended. We need to either unwind the stack or
+ // replay the suspended component.
+ var unitOfWork = workInProgress;
+ var thrownValue = workInProgressThrownValue;
+
+ resumeOrUnwind: switch (workInProgressSuspendedReason) {
+ case SuspendedOnError:
+ {
+ // Unwind then continue with the normal work loop.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
+ break;
+ }
+
+ case SuspendedOnData:
+ {
+ var thenable = thrownValue;
+
+ if (isThenableResolved(thenable)) {
+ // The data resolved. Try rendering the component again.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ replaySuspendedUnitOfWork(unitOfWork);
+ break;
+ } // The work loop is suspended on data. We should wait for it to
+ // resolve before continuing to render.
+ // TODO: Handle the case where the promise resolves synchronously.
+ // Usually this is handled when we instrument the promise to add a
+ // `status` field, but if the promise already has a status, we won't
+ // have added a listener until right here.
- if (previousFiber !== null) {
- var detachedChild = previousFiber.child;
- if (detachedChild !== null) {
- previousFiber.child = null;
+ var onResolution = function () {
+ // Check if the root is still suspended on this promise.
+ if (workInProgressSuspendedReason === SuspendedOnData && workInProgressRoot === root) {
+ // Mark the root as ready to continue rendering.
+ workInProgressSuspendedReason = SuspendedAndReadyToContinue;
+ } // Ensure the root is scheduled. We should do this even if we're
+ // currently working on a different root, so that we resume
+ // rendering later.
- do {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- var detachedSibling = detachedChild.sibling; // $FlowFixMe[incompatible-use] found when upgrading Flow
- detachedChild.sibling = null;
- detachedChild = detachedSibling;
- } while (detachedChild !== null);
- }
- }
-}
+ ensureRootIsScheduled(root);
+ };
-function commitHookPassiveUnmountEffects(finishedWork, nearestMountedAncestor, hookFlags) {
- if (shouldProfile(finishedWork)) {
- startPassiveEffectTimer();
- commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
- recordPassiveEffectDuration(finishedWork);
- } else {
- commitHookEffectListUnmount(hookFlags, finishedWork, nearestMountedAncestor);
- }
-}
+ thenable.then(onResolution, onResolution);
+ break outer;
+ }
-function recursivelyTraversePassiveUnmountEffects(parentFiber) {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects have fired.
- var deletions = parentFiber.deletions;
+ case SuspendedOnImmediate:
+ {
+ // If this fiber just suspended, it's possible the data is already
+ // cached. Yield to the main thread to give it a chance to ping. If
+ // it does, we can retry immediately without unwinding the stack.
+ workInProgressSuspendedReason = SuspendedAndReadyToContinue;
+ break outer;
+ }
- if ((parentFiber.flags & ChildDeletion) !== NoFlags$1) {
- if (deletions !== null) {
- for (var i = 0; i < deletions.length; i++) {
- var childToDelete = deletions[i]; // TODO: Convert this to use recursion
+ case SuspendedOnInstance:
+ {
+ workInProgressSuspendedReason = SuspendedOnInstanceAndReadyToContinue;
+ break outer;
+ }
- nextEffect = childToDelete;
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin(childToDelete, parentFiber);
- }
- }
+ case SuspendedAndReadyToContinue:
+ {
+ var _thenable = thrownValue;
- detachAlternateSiblings(parentFiber);
- }
+ if (isThenableResolved(_thenable)) {
+ // The data resolved. Try rendering the component again.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ replaySuspendedUnitOfWork(unitOfWork);
+ } else {
+ // Otherwise, unwind then continue with the normal work loop.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
+ }
- var prevDebugFiber = getCurrentFiber(); // TODO: Split PassiveMask into separate masks for mount and unmount?
+ break;
+ }
- if (parentFiber.subtreeFlags & PassiveMask) {
- var child = parentFiber.child;
+ case SuspendedOnInstanceAndReadyToContinue:
+ {
+ switch (workInProgress.tag) {
+ case HostComponent:
+ case HostHoistable:
+ case HostSingleton:
+ {
+ // Before unwinding the stack, check one more time if the
+ // instance is ready. It may have loaded when React yielded to
+ // the main thread.
+ // Assigning this to a constant so Flow knows the binding won't
+ // be mutated by `preloadInstance`.
+ var hostFiber = workInProgress;
+ var type = hostFiber.type;
+ var props = hostFiber.pendingProps;
+ var isReady = preloadInstance(type, props);
- while (child !== null) {
- setCurrentFiber(child);
- commitPassiveUnmountOnFiber(child);
- child = child.sibling;
- }
- }
+ if (isReady) {
+ // The data resolved. Resume the work loop as if nothing
+ // suspended. Unlike when a user component suspends, we don't
+ // have to replay anything because the host fiber
+ // already completed.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ var sibling = hostFiber.sibling;
- setCurrentFiber(prevDebugFiber);
-}
+ if (sibling !== null) {
+ workInProgress = sibling;
+ } else {
+ var returnFiber = hostFiber.return;
-function commitPassiveUnmountOnFiber(finishedWork) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
+ if (returnFiber !== null) {
+ workInProgress = returnFiber;
+ completeUnitOfWork(returnFiber);
+ } else {
+ workInProgress = null;
+ }
+ }
- if (finishedWork.flags & Passive$1) {
- commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, Passive | HasEffect);
- }
+ break resumeOrUnwind;
+ }
- break;
- }
+ break;
+ }
- case OffscreenComponent:
- {
- var instance = finishedWork.stateNode;
- var nextState = finishedWork.memoizedState;
- var isHidden = nextState !== null;
+ default:
+ {
+ // This will fail gracefully but it's not correct, so log a
+ // warning in dev.
+ if (true) {
+ error('Unexpected type of fiber triggered a suspensey commit. ' + 'This is a bug in React.');
+ }
- if (isHidden && instance._visibility & OffscreenPassiveEffectsConnected && ( // For backwards compatibility, don't unmount when a tree suspends. In
- // the future we may change this to unmount after a delay.
- finishedWork.return === null || finishedWork.return.tag !== SuspenseComponent)) {
- // The effects are currently connected. Disconnect them.
- // TODO: Add option or heuristic to delay before disconnecting the
- // effects. Then if the tree reappears before the delay has elapsed, we
- // can skip toggling the effects entirely.
- instance._visibility &= ~OffscreenPassiveEffectsConnected;
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- } else {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
- }
+ break;
+ }
+ } // Otherwise, unwind then continue with the normal work loop.
- break;
- }
- default:
- {
- recursivelyTraversePassiveUnmountEffects(finishedWork);
- break;
- }
- }
-}
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
+ break;
+ }
-function recursivelyTraverseDisconnectPassiveEffects(parentFiber) {
- // Deletions effects can be scheduled on any fiber type. They need to happen
- // before the children effects have fired.
- var deletions = parentFiber.deletions;
+ case SuspendedOnDeprecatedThrowPromise:
+ {
+ // Suspended by an old implementation that uses the `throw promise`
+ // pattern. The newer replaying behavior can cause subtle issues
+ // like infinite ping loops. So we maintain the old behavior and
+ // always unwind.
+ workInProgressSuspendedReason = NotSuspended;
+ workInProgressThrownValue = null;
+ throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
+ break;
+ }
- if ((parentFiber.flags & ChildDeletion) !== NoFlags$1) {
- if (deletions !== null) {
- for (var i = 0; i < deletions.length; i++) {
- var childToDelete = deletions[i]; // TODO: Convert this to use recursion
+ case SuspendedOnHydration:
+ {
+ // Selective hydration. An update flowed into a dehydrated tree.
+ // Interrupt the current render so the work loop can switch to the
+ // hydration lane.
+ resetWorkInProgressStack();
+ workInProgressRootExitStatus = RootDidNotComplete;
+ break outer;
+ }
- nextEffect = childToDelete;
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin(childToDelete, parentFiber);
+ default:
+ {
+ throw new Error('Unexpected SuspendedReason. This is a bug in React.');
+ }
+ }
}
- }
-
- detachAlternateSiblings(parentFiber);
- }
- var prevDebugFiber = getCurrentFiber(); // TODO: Check PassiveStatic flag
+ if (true && ReactSharedInternals.actQueue !== null) {
+ // `act` special case: If we're inside an `act` scope, don't consult
+ // `shouldYield`. Always keep working until the render is complete.
+ // This is not just an optimization: in a unit test environment, we
+ // can't trust the result of `shouldYield`, because the host I/O is
+ // likely mocked.
+ workLoopSync();
+ } else {
+ workLoopConcurrent();
+ }
- var child = parentFiber.child;
+ break;
+ } catch (thrownValue) {
+ handleThrow(root, thrownValue);
+ }
+ } while (true);
- while (child !== null) {
- setCurrentFiber(child);
- disconnectPassiveEffect(child);
- child = child.sibling;
- }
+ resetContextDependencies();
+ popDispatcher(prevDispatcher);
+ popAsyncDispatcher(prevAsyncDispatcher);
+ executionContext = prevExecutionContext;
- setCurrentFiber(prevDebugFiber);
-}
-function disconnectPassiveEffect(finishedWork) {
- switch (finishedWork.tag) {
- case FunctionComponent:
- case ForwardRef:
- case SimpleMemoComponent:
- {
- // TODO: Check PassiveStatic flag
- commitHookPassiveUnmountEffects(finishedWork, finishedWork.return, Passive); // When disconnecting passive effects, we fire the effects in the same
- // order as during a deletiong: parent before child
+ if (workInProgress !== null) {
+ // Still work remaining.
+ {
+ markRenderYielded();
+ }
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- break;
- }
+ return RootInProgress;
+ } else {
+ // Completed the tree.
+ {
+ markRenderStopped();
+ } // Set this to null to indicate there's no in-progress render.
- case OffscreenComponent:
- {
- var instance = finishedWork.stateNode;
- if (instance._visibility & OffscreenPassiveEffectsConnected) {
- instance._visibility &= ~OffscreenPassiveEffectsConnected;
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- }
+ workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete.
- break;
- }
+ finishQueueingConcurrentUpdates(); // Return the final exit status.
- default:
- {
- recursivelyTraverseDisconnectPassiveEffects(finishedWork);
- break;
- }
+ return workInProgressRootExitStatus;
}
}
+/** @noinline */
-function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(deletedSubtreeRoot, nearestMountedAncestor) {
- while (nextEffect !== null) {
- var fiber = nextEffect; // Deletion effects fire in parent -> child order
- // TODO: Check if fiber has a PassiveStatic flag
-
- setCurrentFiber(fiber);
- commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
- resetCurrentFiber();
- var child = fiber.child; // TODO: Only traverse subtree if it has a PassiveStatic flag.
- if (child !== null) {
- child.return = fiber;
- nextEffect = child;
- } else {
- commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot);
- }
+function workLoopConcurrent() {
+ // Perform work until Scheduler asks us to yield
+ while (workInProgress !== null && !shouldYield()) {
+ // $FlowFixMe[incompatible-call] found when upgrading Flow
+ performUnitOfWork(workInProgress);
}
}
-function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot) {
- while (nextEffect !== null) {
- var fiber = nextEffect;
- var sibling = fiber.sibling;
- var returnFiber = fiber.return; // Recursively traverse the entire deleted tree and clean up fiber fields.
- // This is more aggressive than ideal, and the long term goal is to only
- // have to detach the deleted tree at the root.
+function performUnitOfWork(unitOfWork) {
+ // The current, flushed, state of this fiber is the alternate. Ideally
+ // nothing should rely on this, but relying on it here means that we don't
+ // need an additional field on the work in progress.
+ var current = unitOfWork.alternate;
+ setCurrentFiber(unitOfWork);
+ var next;
- detachFiberAfterEffects(fiber);
+ if ((unitOfWork.mode & ProfileMode) !== NoMode) {
+ startProfilerTimer(unitOfWork);
+ next = beginWork(current, unitOfWork, entangledRenderLanes);
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
+ } else {
+ next = beginWork(current, unitOfWork, entangledRenderLanes);
+ }
- if (fiber === deletedSubtreeRoot) {
- nextEffect = null;
- return;
- }
+ resetCurrentFiber();
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
- if (sibling !== null) {
- sibling.return = returnFiber;
- nextEffect = sibling;
- return;
- }
+ if (next === null) {
+ // If this doesn't spawn new work, complete the current work.
+ completeUnitOfWork(unitOfWork);
+ } else {
+ workInProgress = next;
+ }
- nextEffect = returnFiber;
+ {
+ setCurrentOwner(null);
}
}
-function commitPassiveUnmountInsideDeletedTreeOnFiber(current, nearestMountedAncestor) {
- switch (current.tag) {
- case FunctionComponent:
- case ForwardRef:
+function replaySuspendedUnitOfWork(unitOfWork) {
+ // This is a fork of performUnitOfWork specifcally for replaying a fiber that
+ // just suspended.
+ //
+ var current = unitOfWork.alternate;
+ setCurrentFiber(unitOfWork);
+ var next;
+ setCurrentFiber(unitOfWork);
+ var isProfilingMode = (unitOfWork.mode & ProfileMode) !== NoMode;
+
+ if (isProfilingMode) {
+ startProfilerTimer(unitOfWork);
+ }
+
+ switch (unitOfWork.tag) {
case SimpleMemoComponent:
+ case FunctionComponent:
{
- commitHookPassiveUnmountEffects(current, nearestMountedAncestor, Passive);
+ // Resolve `defaultProps`. This logic is copied from `beginWork`.
+ // TODO: Consider moving this switch statement into that module. Also,
+ // could maybe use this as an opportunity to say `use` doesn't work with
+ // `defaultProps` :)
+ var Component = unitOfWork.type;
+ var unresolvedProps = unitOfWork.pendingProps;
+ var resolvedProps = unresolvedProps ;
+ var context;
+
+ next = replayFunctionComponent(current, unitOfWork, resolvedProps, Component, context, workInProgressRootRenderLanes);
break;
}
- // TODO: run passive unmount effects when unmounting a root.
- // Because passive unmount effects are not currently run,
- // the cache instance owned by the root will never be freed.
- // When effects are run, the cache should be freed here:
- // case HostRoot: {
- // if (enableCache) {
- // const cache = current.memoizedState.cache;
- // releaseCache(cache);
- // }
- // break;
- // }
- case LegacyHiddenComponent:
- case OffscreenComponent:
+ case ForwardRef:
{
- {
- if (current.memoizedState !== null && current.memoizedState.cachePool !== null) {
- var cache = current.memoizedState.cachePool.pool; // Retain/release the cache used for pending (suspended) nodes.
- // Note that this is only reached in the non-suspended/visible case:
- // when the content is suspended/hidden, the retain/release occurs
- // via the parent Suspense component (see case above).
+ // Resolve `defaultProps`. This logic is copied from `beginWork`.
+ // TODO: Consider moving this switch statement into that module. Also,
+ // could maybe use this as an opportunity to say `use` doesn't work with
+ // `defaultProps` :)
+ var _Component = unitOfWork.type.render;
+ var _unresolvedProps = unitOfWork.pendingProps;
- if (cache != null) {
- retainCache(cache);
- }
- }
- }
+ var _resolvedProps = _unresolvedProps ;
+ next = replayFunctionComponent(current, unitOfWork, _resolvedProps, _Component, unitOfWork.ref, workInProgressRootRenderLanes);
break;
}
- case SuspenseComponent:
+ case HostComponent:
{
-
- break;
+ // Some host components are stateful (that's how we implement form
+ // actions) but we don't bother to reuse the memoized state because it's
+ // not worth the extra code. The main reason to reuse the previous hooks
+ // is to reuse uncached promises, but we happen to know that the only
+ // promises that a host component might suspend on are definitely cached
+ // because they are controlled by us. So don't bother.
+ resetHooksOnUnwind(unitOfWork); // Fallthrough to the next branch.
}
- case CacheComponent:
+ default:
{
- {
- var _cache = current.memoizedState.cache;
- releaseCache(_cache);
- }
-
+ // Other types besides function components are reset completely before
+ // being replayed. Currently this only happens when a Usable type is
+ // reconciled — the reconciler will suspend.
+ //
+ // We reset the fiber back to its original state; however, this isn't
+ // a full "unwind" because we're going to reuse the promises that were
+ // reconciled previously. So it's intentional that we don't call
+ // resetSuspendedWorkLoopOnUnwind here.
+ unwindInterruptedWork(current, unitOfWork);
+ unitOfWork = workInProgress = resetWorkInProgress(unitOfWork, entangledRenderLanes);
+ next = beginWork(current, unitOfWork, entangledRenderLanes);
break;
- }
- }
-}
-
-var hasBadMapPolyfill;
-
-{
- hasBadMapPolyfill = false;
-
- try {
- var nonExtensibleObject = Object.preventExtensions({});
- /* eslint-disable no-new */
-
- new Map([[nonExtensibleObject, null]]);
- new Set([nonExtensibleObject]);
- /* eslint-enable no-new */
- } catch (e) {
- // TODO: Consider warning about bad polyfills
- hasBadMapPolyfill = true;
- }
-}
-
-function FiberNode(tag, pendingProps, key, mode) {
- // Instance
- this.tag = tag;
- this.key = key;
- this.elementType = null;
- this.type = null;
- this.stateNode = null; // Fiber
-
- this.return = null;
- this.child = null;
- this.sibling = null;
- this.index = 0;
- this.ref = null;
- this.refCleanup = null;
- this.pendingProps = pendingProps;
- this.memoizedProps = null;
- this.updateQueue = null;
- this.memoizedState = null;
- this.dependencies = null;
- this.mode = mode; // Effects
+ }
+ }
- this.flags = NoFlags$1;
- this.subtreeFlags = NoFlags$1;
- this.deletions = null;
- this.lanes = NoLanes;
- this.childLanes = NoLanes;
- this.alternate = null;
+ if (isProfilingMode) {
+ stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
+ } // The begin phase finished successfully without suspending. Return to the
+ // normal work loop.
- {
- // Note: The following is done to avoid a v8 performance cliff.
- //
- // Initializing the fields below to smis and later updating them with
- // double values will cause Fibers to end up having separate shapes.
- // This behavior/bug has something to do with Object.preventExtension().
- // Fortunately this only impacts DEV builds.
- // Unfortunately it makes React unusably slow for some applications.
- // To work around this, initialize the fields below with doubles.
- //
- // Learn more about this here:
- // https://github.com/facebook/react/issues/14365
- // https://bugs.chromium.org/p/v8/issues/detail?id=8538
- this.actualDuration = Number.NaN;
- this.actualStartTime = Number.NaN;
- this.selfBaseDuration = Number.NaN;
- this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization.
- // This won't trigger the performance cliff mentioned above,
- // and it simplifies other profiler code (including DevTools).
- this.actualDuration = 0;
- this.actualStartTime = -1;
- this.selfBaseDuration = 0;
- this.treeBaseDuration = 0;
+ resetCurrentFiber();
+ unitOfWork.memoizedProps = unitOfWork.pendingProps;
+
+ if (next === null) {
+ // If this doesn't spawn new work, complete the current work.
+ completeUnitOfWork(unitOfWork);
+ } else {
+ workInProgress = next;
}
{
- // This isn't directly used but is handy for debugging internals:
- this._debugInfo = null;
- this._debugOwner = null;
- this._debugNeedsRemount = false;
- this._debugHookTypes = null;
+ setCurrentOwner(null);
+ }
+}
- if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
- Object.preventExtensions(this);
+function throwAndUnwindWorkLoop(root, unitOfWork, thrownValue) {
+ // This is a fork of performUnitOfWork specifcally for unwinding a fiber
+ // that threw an exception.
+ //
+ // Return to the normal work loop. This will unwind the stack, and potentially
+ // result in showing a fallback.
+ resetSuspendedWorkLoopOnUnwind(unitOfWork);
+ var returnFiber = unitOfWork.return;
+
+ try {
+ // Find and mark the nearest Suspense or error boundary that can handle
+ // this "exception".
+ var didFatal = throwException(root, returnFiber, unitOfWork, thrownValue, workInProgressRootRenderLanes);
+
+ if (didFatal) {
+ panicOnRootError(root, thrownValue);
+ return;
+ }
+ } catch (error) {
+ // We had trouble processing the error. An example of this happening is
+ // when accessing the `componentDidCatch` property of an error boundary
+ // throws an error. A weird edge case. There's a regression test for this.
+ // To prevent an infinite loop, bubble the error up to the next parent.
+ if (returnFiber !== null) {
+ workInProgress = returnFiber;
+ throw error;
+ } else {
+ panicOnRootError(root, thrownValue);
+ return;
}
}
-} // This is a constructor function, rather than a POJO constructor, still
-// please ensure we do the following:
-// 1) Nobody should add any instance methods on this. Instance methods can be
-// more difficult to predict when they get optimized and they are almost
-// never inlined properly in static compilers.
-// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
-// always know when it is a fiber.
-// 3) We might want to experiment with using numeric keys since they are easier
-// to optimize in a non-JIT environment.
-// 4) We can easily go from a constructor to a createFiber object literal if that
-// is faster.
-// 5) It should be easy to port this to a C struct and keep a C implementation
-// compatible.
-
-function createFiber(tag, pendingProps, key, mode) {
- // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
- return new FiberNode(tag, pendingProps, key, mode);
+ if (unitOfWork.flags & Incomplete) {
+ // Unwind the stack until we reach the nearest boundary.
+ unwindUnitOfWork(unitOfWork);
+ } else {
+ // Although the fiber suspended, we're intentionally going to commit it in
+ // an inconsistent state. We can do this safely in cases where we know the
+ // inconsistent tree will be hidden.
+ //
+ // This currently only applies to Legacy Suspense implementation, but we may
+ // port a version of this to concurrent roots, too, when performing a
+ // synchronous render. Because that will allow us to mutate the tree as we
+ // go instead of buffering mutations until the end. Though it's unclear if
+ // this particular path is how that would be implemented.
+ completeUnitOfWork(unitOfWork);
+ }
}
-function shouldConstruct(Component) {
- var prototype = Component.prototype;
- return !!(prototype && prototype.isReactComponent);
-}
+function panicOnRootError(root, error) {
+ // There's no ancestor that can handle this exception. This should never
+ // happen because the root is supposed to capture all errors that weren't
+ // caught by an error boundary. This is a fatal error, or panic condition,
+ // because we've run out of ways to recover.
+ workInProgressRootExitStatus = RootFatalErrored;
+ logUncaughtError(root, createCapturedValueAtFiber(error, root.current)); // Set `workInProgress` to null. This represents advancing to the next
+ // sibling, or the parent if there are no siblings. But since the root
+ // has no siblings nor a parent, we set it to null. Usually this is
+ // handled by `completeUnitOfWork` or `unwindWork`, but since we're
+ // intentionally not calling those, we need set it here.
+ // TODO: Consider calling `unwindWork` to pop the contexts.
-function isSimpleFunctionComponent(type) {
- return typeof type === 'function' && !shouldConstruct(type) && type.defaultProps === undefined;
+ workInProgress = null;
}
-function isFunctionClassComponent(type) {
- return shouldConstruct(type);
-} // This is used to create an alternate fiber to do work on.
-function createWorkInProgress(current, pendingProps) {
- var workInProgress = current.alternate;
-
- if (workInProgress === null) {
- // We use a double buffering pooling technique because we know that we'll
- // only ever need at most two versions of a tree. We pool the "other" unused
- // node that we're free to reuse. This is lazily created to avoid allocating
- // extra objects for things that are never updated. It also allow us to
- // reclaim the extra memory if needed.
- workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
- workInProgress.elementType = current.elementType;
- workInProgress.type = current.type;
- workInProgress.stateNode = current.stateNode;
+function completeUnitOfWork(unitOfWork) {
+ // Attempt to complete the current unit of work, then move to the next
+ // sibling. If there are no more siblings, return to the parent fiber.
+ var completedWork = unitOfWork;
+ do {
{
- // DEV-only fields
- workInProgress._debugOwner = current._debugOwner;
- workInProgress._debugHookTypes = current._debugHookTypes;
- }
+ if ((completedWork.flags & Incomplete) !== NoFlags$1) {
+ // NOTE: If we re-enable sibling prerendering in some cases, this branch
+ // is where we would switch to the unwinding path.
+ error('Internal React error: Expected this fiber to be complete, but ' + "it isn't. It should have been unwound. This is a bug in React.");
+ }
+ } // The current, flushed, state of this fiber is the alternate. Ideally
+ // nothing should rely on this, but relying on it here means that we don't
+ // need an additional field on the work in progress.
- workInProgress.alternate = current;
- current.alternate = workInProgress;
- } else {
- workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
- workInProgress.type = current.type; // We already have an alternate.
- // Reset the effect tag.
+ var current = completedWork.alternate;
+ var returnFiber = completedWork.return;
+ setCurrentFiber(completedWork);
+ var next = void 0;
- workInProgress.flags = NoFlags$1; // The effects are no longer valid.
+ if ((completedWork.mode & ProfileMode) === NoMode) {
+ next = completeWork(current, completedWork, entangledRenderLanes);
+ } else {
+ startProfilerTimer(completedWork);
+ next = completeWork(current, completedWork, entangledRenderLanes); // Update render duration assuming we didn't error.
- workInProgress.subtreeFlags = NoFlags$1;
- workInProgress.deletions = null;
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
+ }
- {
- // We intentionally reset, rather than copy, actualDuration & actualStartTime.
- // This prevents time from endlessly accumulating in new commits.
- // This has the downside of resetting values for different priority renders,
- // But works for yielding (the common case) and should support resuming.
- workInProgress.actualDuration = 0;
- workInProgress.actualStartTime = -1;
+ resetCurrentFiber();
+
+ if (next !== null) {
+ // Completing this fiber spawned new work. Work on that next.
+ workInProgress = next;
+ return;
}
- } // Reset all effects except static ones.
- // Static effects are not specific to a render.
+ var siblingFiber = completedWork.sibling;
- workInProgress.flags = current.flags & StaticMask;
- workInProgress.childLanes = current.childLanes;
- workInProgress.lanes = current.lanes;
- workInProgress.child = current.child;
- workInProgress.memoizedProps = current.memoizedProps;
- workInProgress.memoizedState = current.memoizedState;
- workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
- // it cannot be shared with the current fiber.
+ if (siblingFiber !== null) {
+ // If there is more work to do in this returnFiber, do that next.
+ workInProgress = siblingFiber;
+ return;
+ } // Otherwise, return to the parent
+ // $FlowFixMe[incompatible-type] we bail out when we get a null
- var currentDependencies = current.dependencies;
- workInProgress.dependencies = currentDependencies === null ? null : {
- lanes: currentDependencies.lanes,
- firstContext: currentDependencies.firstContext
- }; // These will be overridden during the parent's reconciliation
- workInProgress.sibling = current.sibling;
- workInProgress.index = current.index;
- workInProgress.ref = current.ref;
- workInProgress.refCleanup = current.refCleanup;
+ completedWork = returnFiber; // Update the next thing we're working on in case something throws.
- {
- workInProgress.selfBaseDuration = current.selfBaseDuration;
- workInProgress.treeBaseDuration = current.treeBaseDuration;
+ workInProgress = completedWork;
+ } while (completedWork !== null); // We've reached the root.
+
+
+ if (workInProgressRootExitStatus === RootInProgress) {
+ workInProgressRootExitStatus = RootCompleted;
}
+}
+
+function unwindUnitOfWork(unitOfWork) {
+ var incompleteWork = unitOfWork;
+
+ do {
+ // The current, flushed, state of this fiber is the alternate. Ideally
+ // nothing should rely on this, but relying on it here means that we don't
+ // need an additional field on the work in progress.
+ var current = incompleteWork.alternate; // This fiber did not complete because something threw. Pop values off
+ // the stack without entering the complete phase. If this is a boundary,
+ // capture values if possible.
+
+ var next = unwindWork(current, incompleteWork); // Because this fiber did not complete, don't reset its lanes.
+
+ if (next !== null) {
+ // Found a boundary that can handle this exception. Re-renter the
+ // begin phase. This branch will return us to the normal work loop.
+ //
+ // Since we're restarting, remove anything that is not a host effect
+ // from the effect tag.
+ next.flags &= HostEffectMask;
+ workInProgress = next;
+ return;
+ } // Keep unwinding until we reach either a boundary or the root.
- {
- workInProgress._debugInfo = current._debugInfo;
- workInProgress._debugNeedsRemount = current._debugNeedsRemount;
- switch (workInProgress.tag) {
- case FunctionComponent:
- case SimpleMemoComponent:
- workInProgress.type = resolveFunctionForHotReloading(current.type);
- break;
+ if ((incompleteWork.mode & ProfileMode) !== NoMode) {
+ // Record the render duration for the fiber that errored.
+ stopProfilerTimerIfRunningAndRecordDelta(incompleteWork, false); // Include the time spent working on failed children before continuing.
- case ClassComponent:
- workInProgress.type = resolveClassForHotReloading(current.type);
- break;
+ var actualDuration = incompleteWork.actualDuration;
+ var child = incompleteWork.child;
- case ForwardRef:
- workInProgress.type = resolveForwardRefForHotReloading(current.type);
- break;
- }
- }
+ while (child !== null) {
+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+ actualDuration += child.actualDuration;
+ child = child.sibling;
+ }
- return workInProgress;
-} // Used to reuse a Fiber for a second pass.
+ incompleteWork.actualDuration = actualDuration;
+ } // TODO: Once we stop prerendering siblings, instead of resetting the parent
+ // of the node being unwound, we should be able to reset node itself as we
+ // unwind the stack. Saves an additional null check.
-function resetWorkInProgress(workInProgress, renderLanes) {
- // This resets the Fiber to what createFiber or createWorkInProgress would
- // have set the values to before during the first pass. Ideally this wouldn't
- // be necessary but unfortunately many code paths reads from the workInProgress
- // when they should be reading from current and writing to workInProgress.
- // We assume pendingProps, index, key, ref, return are still untouched to
- // avoid doing another reconciliation.
- // Reset the effect flags but keep any Placement tags, since that's something
- // that child fiber is setting, not the reconciliation.
- workInProgress.flags &= StaticMask | Placement; // The effects are no longer valid.
- var current = workInProgress.alternate;
+ var returnFiber = incompleteWork.return;
- if (current === null) {
- // Reset to createFiber's initial values.
- workInProgress.childLanes = NoLanes;
- workInProgress.lanes = renderLanes;
- workInProgress.child = null;
- workInProgress.subtreeFlags = NoFlags$1;
- workInProgress.memoizedProps = null;
- workInProgress.memoizedState = null;
- workInProgress.updateQueue = null;
- workInProgress.dependencies = null;
- workInProgress.stateNode = null;
+ if (returnFiber !== null) {
+ // Mark the parent fiber as incomplete and clear its subtree flags.
+ // TODO: Once we stop prerendering siblings, we may be able to get rid of
+ // the Incomplete flag because unwinding to the nearest boundary will
+ // happen synchronously.
+ returnFiber.flags |= Incomplete;
+ returnFiber.subtreeFlags = NoFlags$1;
+ returnFiber.deletions = null;
+ } // NOTE: If we re-enable sibling prerendering in some cases, here we
+ // would switch to the normal completion path: check if a sibling
+ // exists, and if so, begin work on it.
+ // Otherwise, return to the parent
+ // $FlowFixMe[incompatible-type] we bail out when we get a null
- {
- // Note: We don't reset the actualTime counts. It's useful to accumulate
- // actual time across multiple render passes.
- workInProgress.selfBaseDuration = 0;
- workInProgress.treeBaseDuration = 0;
- }
- } else {
- // Reset to the cloned values that createWorkInProgress would've.
- workInProgress.childLanes = current.childLanes;
- workInProgress.lanes = current.lanes;
- workInProgress.child = current.child;
- workInProgress.subtreeFlags = NoFlags$1;
- workInProgress.deletions = null;
- workInProgress.memoizedProps = current.memoizedProps;
- workInProgress.memoizedState = current.memoizedState;
- workInProgress.updateQueue = current.updateQueue; // Needed because Blocks store data on type.
- workInProgress.type = current.type; // Clone the dependencies object. This is mutated during the render phase, so
- // it cannot be shared with the current fiber.
+ incompleteWork = returnFiber; // Update the next thing we're working on in case something throws.
- var currentDependencies = current.dependencies;
- workInProgress.dependencies = currentDependencies === null ? null : {
- lanes: currentDependencies.lanes,
- firstContext: currentDependencies.firstContext
- };
+ workInProgress = incompleteWork;
+ } while (incompleteWork !== null); // We've unwound all the way to the root.
- {
- // Note: We don't reset the actualTime counts. It's useful to accumulate
- // actual time across multiple render passes.
- workInProgress.selfBaseDuration = current.selfBaseDuration;
- workInProgress.treeBaseDuration = current.treeBaseDuration;
- }
- }
- return workInProgress;
+ workInProgressRootExitStatus = RootDidNotComplete;
+ workInProgress = null;
}
-function createHostRootFiber(tag, isStrictMode, concurrentUpdatesByDefaultOverride) {
- var mode;
-
- {
- mode = ConcurrentMode;
- if (isStrictMode === true) {
- mode |= StrictLegacyMode | StrictEffectsMode;
- }
- }
+function commitRoot(root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, spawnedLane) {
+ // TODO: This no longer makes any sense. We already wrap the mutation and
+ // layout phases. Should be able to remove.
+ var prevTransition = ReactSharedInternals.T;
+ var previousUpdateLanePriority = getCurrentUpdatePriority();
- if (isDevToolsPresent) {
- // Always collect profile timings when DevTools are present.
- // This enables DevTools to start capturing timing at any point–
- // Without some nodes in the tree having empty base times.
- mode |= ProfileMode;
+ try {
+ setCurrentUpdatePriority(DiscreteEventPriority);
+ ReactSharedInternals.T = null;
+ commitRootImpl(root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, previousUpdateLanePriority, spawnedLane);
+ } finally {
+ ReactSharedInternals.T = prevTransition;
+ setCurrentUpdatePriority(previousUpdateLanePriority);
}
- return createFiber(HostRoot, null, null, mode);
+ return null;
}
-function createFiberFromTypeAndProps(type, // React$ElementType
-key, pendingProps, owner, mode, lanes) {
- var fiberTag = FunctionComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy.
-
- var resolvedType = type;
- if (typeof type === 'function') {
- if (shouldConstruct(type)) {
- fiberTag = ClassComponent;
+function commitRootImpl(root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate, renderPriorityLevel, spawnedLane) {
+ do {
+ // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
+ // means `flushPassiveEffects` will sometimes result in additional
+ // passive effects. So we need to keep flushing in a loop until there are
+ // no more pending effects.
+ // TODO: Might be better if `flushPassiveEffects` did not automatically
+ // flush synchronous work at the end, to avoid factoring hazards like this.
+ flushPassiveEffects();
+ } while (rootWithPendingPassiveEffects !== null);
- {
- resolvedType = resolveClassForHotReloading(resolvedType);
- }
- } else {
- {
- resolvedType = resolveFunctionForHotReloading(resolvedType);
- }
- }
- } else if (typeof type === 'string') {
- {
- var hostContext = getHostContext();
- fiberTag = isHostHoistableType(type, pendingProps, hostContext) ? HostHoistable : isHostSingletonType(type) ? HostSingleton : HostComponent;
- }
- } else {
- getTag: switch (type) {
- case REACT_FRAGMENT_TYPE:
- return createFiberFromFragment(pendingProps.children, mode, lanes, key);
+ flushRenderPhaseStrictModeWarningsInDEV();
- case REACT_STRICT_MODE_TYPE:
- fiberTag = Mode;
- mode |= StrictLegacyMode;
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
+ throw new Error('Should not already be working.');
+ }
- {
- // Strict effects should never run on legacy roots
- mode |= StrictEffectsMode;
- }
+ var finishedWork = root.finishedWork;
+ var lanes = root.finishedLanes;
- break;
+ {
+ markCommitStarted(lanes);
+ }
- case REACT_PROFILER_TYPE:
- return createFiberFromProfiler(pendingProps, mode, lanes, key);
+ if (finishedWork === null) {
- case REACT_SUSPENSE_TYPE:
- return createFiberFromSuspense(pendingProps, mode, lanes, key);
+ {
+ markCommitStopped();
+ }
- case REACT_SUSPENSE_LIST_TYPE:
- return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
+ return null;
+ } else {
+ {
+ if (lanes === NoLanes) {
+ error('root.finishedLanes should not be empty during a commit. This is a ' + 'bug in React.');
+ }
+ }
+ }
- case REACT_OFFSCREEN_TYPE:
- return createFiberFromOffscreen(pendingProps, mode, lanes, key);
+ root.finishedWork = null;
+ root.finishedLanes = NoLanes;
- case REACT_LEGACY_HIDDEN_TYPE:
+ if (finishedWork === root.current) {
+ throw new Error('Cannot commit the same tree as before. This error is likely caused by ' + 'a bug in React. Please file an issue.');
+ } // commitRoot never returns a continuation; it always finishes synchronously.
+ // So we can clear these now to allow a new callback to be scheduled.
- // Fall through
- case REACT_SCOPE_TYPE:
+ root.callbackNode = null;
+ root.callbackPriority = NoLane;
+ root.cancelPendingCommit = null; // Check which lanes no longer have any work scheduled on them, and mark
+ // those as finished.
- // Fall through
+ var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); // Make sure to account for lanes that were updated by a concurrent event
+ // during the render phase; don't mark them as finished.
- case REACT_TRACING_MARKER_TYPE:
+ var concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
+ remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
+ markRootFinished(root, remainingLanes, spawnedLane); // Reset this before firing side effects so we can detect recursive updates.
- // Fall through
+ didIncludeCommitPhaseUpdate = false;
- case REACT_DEBUG_TRACING_MODE_TYPE:
+ if (root === workInProgressRoot) {
+ // We can reset these now that they are finished.
+ workInProgressRoot = null;
+ workInProgress = null;
+ workInProgressRootRenderLanes = NoLanes;
+ } // If there are pending passive effects, schedule a callback to process them.
+ // Do this as early as possible, so it is queued before anything else that
+ // might get scheduled in the commit phase. (See #16714.)
+ // TODO: Delete all other places that schedule the passive effect callback
+ // They're redundant.
- // Fall through
- default:
- {
- if (typeof type === 'object' && type !== null) {
- switch (type.$$typeof) {
- case REACT_PROVIDER_TYPE:
+ if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags$1 || (finishedWork.flags & PassiveMask) !== NoFlags$1) {
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ pendingPassiveEffectsRemainingLanes = remainingLanes; // workInProgressTransitions might be overwritten, so we want
+ // to store it in pendingPassiveTransitions until they get processed
+ // We need to pass this through as an argument to commitRoot
+ // because workInProgressTransitions might have changed between
+ // the previous render and commit if we throttle the commit
+ // with setTimeout
- // Fall through
+ pendingPassiveTransitions = transitions;
+ scheduleCallback$1(NormalPriority$1, function () {
+ flushPassiveEffects(); // This render triggered passive effects: release the root cache pool
+ // *after* passive effects fire to avoid freeing a cache pool that may
+ // be referenced by a node in the tree (HostRoot, Cache boundary etc)
- case REACT_CONTEXT_TYPE:
- {
- fiberTag = ContextProvider;
- break getTag;
- }
+ return null;
+ });
+ }
+ } // Check if there are any effects in the whole tree.
+ // TODO: This is left over from the effect list implementation, where we had
+ // to check for the existence of `firstEffect` to satisfy Flow. I think the
+ // only other reason this optimization exists is because it affects profiling.
+ // Reconsider whether this is necessary.
- case REACT_CONSUMER_TYPE:
- {
- fiberTag = ContextConsumer;
- break getTag;
- }
- // Fall through
+ var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags$1;
+ var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags$1;
- case REACT_FORWARD_REF_TYPE:
- fiberTag = ForwardRef;
+ if (subtreeHasEffects || rootHasEffect) {
+ var prevTransition = ReactSharedInternals.T;
+ ReactSharedInternals.T = null;
+ var previousPriority = getCurrentUpdatePriority();
+ setCurrentUpdatePriority(DiscreteEventPriority);
+ var prevExecutionContext = executionContext;
+ executionContext |= CommitContext; // Reset this to null before calling lifecycles
- {
- resolvedType = resolveForwardRefForHotReloading(resolvedType);
- }
+ {
+ setCurrentOwner(null);
+ } // The commit phase is broken into several sub-phases. We do a separate pass
+ // of the effect list for each phase: all mutation effects come before all
+ // layout effects, and so on.
+ // The first phase a "before mutation" phase. We use this phase to read the
+ // state of the host tree right before we mutate it. This is where
+ // getSnapshotBeforeUpdate is called.
- break getTag;
- case REACT_MEMO_TYPE:
- fiberTag = MemoComponent;
- break getTag;
+ commitBeforeMutationEffects(root, finishedWork);
- case REACT_LAZY_TYPE:
- fiberTag = LazyComponent;
- resolvedType = null;
- break getTag;
- }
- }
+ {
+ // Mark the current commit time to be shared by all Profilers in this
+ // batch. This enables them to be grouped later.
+ recordCommitTime();
+ } // The next phase is the mutation phase, where we mutate the host tree.
- var info = '';
- {
- if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
- info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and " + 'named imports.';
- }
+ commitMutationEffects(root, finishedWork, lanes);
- var ownerName = owner ? getComponentNameFromOwner(owner) : null;
+ resetAfterCommit(); // The work-in-progress tree is now the current tree. This must come after
+ // the mutation phase, so that the previous tree is still current during
+ // componentWillUnmount, but before the layout phase, so that the finished
+ // work is current during componentDidMount/Update.
- if (ownerName) {
- info += '\n\nCheck the render method of `' + ownerName + '`.';
- }
- }
+ root.current = finishedWork; // The next phase is the layout phase, where we call effects that read
- throw new Error('Element type is invalid: expected a string (for built-in ' + 'components) or a class/function (for composite components) ' + ("but got: " + (type == null ? type : typeof type) + "." + info));
- }
+ {
+ markLayoutEffectsStarted(lanes);
}
- }
-
- var fiber = createFiber(fiberTag, pendingProps, key, mode);
- fiber.elementType = type;
- fiber.type = resolvedType;
- fiber.lanes = lanes;
- {
- fiber._debugOwner = owner;
- }
-
- return fiber;
-}
-function createFiberFromElement(element, mode, lanes) {
- var owner = null;
+ commitLayoutEffects(finishedWork, root, lanes);
- {
- owner = element._owner;
- }
+ {
+ markLayoutEffectsStopped();
+ } // Tell Scheduler to yield at the end of the frame, so the browser has an
+ // opportunity to paint.
- var type = element.type;
- var key = element.key;
- var pendingProps = element.props;
- var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
- {
- fiber._debugOwner = element._owner;
- }
+ requestPaint();
+ executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value.
- return fiber;
-}
-function createFiberFromFragment(elements, mode, lanes, key) {
- var fiber = createFiber(Fragment, elements, key, mode);
- fiber.lanes = lanes;
- return fiber;
-}
+ setCurrentUpdatePriority(previousPriority);
+ ReactSharedInternals.T = prevTransition;
+ } else {
+ // No effects.
+ root.current = finishedWork; // Measure these anyway so the flamegraph explicitly shows that there were
+ // no effects.
+ // TODO: Maybe there's a better way to report this.
-function createFiberFromProfiler(pendingProps, mode, lanes, key) {
- {
- if (typeof pendingProps.id !== 'string') {
- error('Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.', typeof pendingProps.id);
+ {
+ recordCommitTime();
}
}
- var fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
- fiber.elementType = REACT_PROFILER_TYPE;
- fiber.lanes = lanes;
-
- {
- fiber.stateNode = {
- effectDuration: 0,
- passiveEffectDuration: 0
- };
- }
+ var rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
- return fiber;
-}
+ if (rootDoesHavePassiveEffects) {
+ // This commit has passive effects. Stash a reference to them. But don't
+ // schedule a callback until after flushing layout work.
+ rootDoesHavePassiveEffects = false;
+ rootWithPendingPassiveEffects = root;
+ pendingPassiveEffectsLanes = lanes;
+ } else {
+ // There were no passive effects, so we can immediately release the cache
+ // pool for this render.
+ releaseRootPooledCache(root, remainingLanes);
-function createFiberFromSuspense(pendingProps, mode, lanes, key) {
- var fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
- fiber.elementType = REACT_SUSPENSE_TYPE;
- fiber.lanes = lanes;
- return fiber;
-}
-function createFiberFromSuspenseList(pendingProps, mode, lanes, key) {
- var fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
- fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
- fiber.lanes = lanes;
- return fiber;
-}
-function createFiberFromOffscreen(pendingProps, mode, lanes, key) {
- var fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
- fiber.elementType = REACT_OFFSCREEN_TYPE;
- fiber.lanes = lanes;
- var primaryChildInstance = {
- _visibility: OffscreenVisible,
- _pendingVisibility: OffscreenVisible,
- _pendingMarkers: null,
- _retryCache: null,
- _transitions: null,
- _current: null,
- detach: function () {
- return detachOffscreenInstance(primaryChildInstance);
- },
- attach: function () {
- return attachOffscreenInstance(primaryChildInstance);
+ {
+ nestedPassiveUpdateCount = 0;
+ rootWithPassiveNestedUpdates = null;
}
- };
- fiber.stateNode = primaryChildInstance;
- return fiber;
-}
-function createFiberFromText(content, mode, lanes) {
- var fiber = createFiber(HostText, content, null, mode);
- fiber.lanes = lanes;
- return fiber;
-}
-function createFiberFromDehydratedFragment(dehydratedNode) {
- var fiber = createFiber(DehydratedFragment, null, null, NoMode);
- fiber.stateNode = dehydratedNode;
- return fiber;
-}
-function createFiberFromPortal(portal, mode, lanes) {
- var pendingProps = portal.children !== null ? portal.children : [];
- var fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
- fiber.lanes = lanes;
- fiber.stateNode = {
- containerInfo: portal.containerInfo,
- pendingChildren: null,
- // Used by persistent updates
- implementation: portal.implementation
- };
- return fiber;
-}
-
-// This is imported by the event replaying implementation in React DOM. It's
-// in a separate file to break a circular dependency between the renderer and
-// the reconciler.
-function isRootDehydrated(root) {
- var currentState = root.current.memoizedState;
- return currentState.isDehydrated;
-}
-
-/**
- * Tag the fiber with an update effect. This turns a Placement into
- * a PlacementAndUpdate.
- */
-
-function markUpdate(workInProgress) {
- workInProgress.flags |= Update;
-}
-
-function appendAllChildren(parent, workInProgress, needsVisibilityToggle, isHidden) {
- {
- // We only have the top Fiber that was created but we need recurse down its
- // children to find all the terminal nodes.
- var node = workInProgress.child;
-
- while (node !== null) {
- if (node.tag === HostComponent || node.tag === HostText) {
- appendInitialChild(parent, node.stateNode);
- } else if (node.tag === HostPortal || (node.tag === HostSingleton )) ; else if (node.child !== null) {
- node.child.return = node;
- node = node.child;
- continue;
- }
+ } // Read this again, since an effect might have updated it
- if (node === workInProgress) {
- return;
- } // $FlowFixMe[incompatible-use] found when upgrading Flow
+ remainingLanes = root.pendingLanes; // Check if there's remaining work on this root
+ // TODO: This is part of the `componentDidCatch` implementation. Its purpose
+ // is to detect whether something might have called setState inside
+ // `componentDidCatch`. The mechanism is known to be flawed because `setState`
+ // inside `componentDidCatch` is itself flawed — that's why we recommend
+ // `getDerivedStateFromError` instead. However, it could be improved by
+ // checking if remainingLanes includes Sync work, instead of whether there's
+ // any work remaining at all (which would also include stuff like Suspense
+ // retries or transitions). It's been like this for a while, though, so fixing
+ // it probably isn't that urgent.
- while (node.sibling === null) {
- // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (node.return === null || node.return === workInProgress) {
- return;
- }
+ if (remainingLanes === NoLanes) {
+ // If there's no remaining work, we can clear the set of already failed
+ // error boundaries.
+ legacyErrorBoundariesThatAlreadyFailed = null;
+ }
- node = node.return;
- } // $FlowFixMe[incompatible-use] found when upgrading Flow
+ {
+ if (!rootDidHavePassiveEffects) {
+ commitDoubleInvokeEffectsInDEV(root);
+ }
+ }
+ onCommitRoot$1(finishedWork.stateNode, renderPriorityLevel);
- node.sibling.return = node.return;
- node = node.sibling;
+ {
+ if (isDevToolsPresent) {
+ root.memoizedUpdaters.clear();
}
}
-} // An unfortunate fork of appendAllChildren because we have two different parent types.
-function updateHostComponent(current, workInProgress, type, newProps, renderLanes) {
{
- // If we have an alternate, that means this is an update and we need to
- // schedule a side-effect to do the updates.
- var oldProps = current.memoizedProps;
+ onCommitRoot();
+ } // Always call this before exiting `commitRoot`, to ensure that any
+ // additional work on this root is scheduled.
- if (oldProps === newProps) {
- // In mutation mode, this is sufficient for a bailout because
- // we won't touch this node even if children changed.
- return;
+
+ ensureRootIsScheduled(root);
+
+ if (recoverableErrors !== null) {
+ // There were errors during this render, but recovered from them without
+ // needing to surface it to the UI. We log them here.
+ var onRecoverableError = root.onRecoverableError;
+
+ for (var i = 0; i < recoverableErrors.length; i++) {
+ var recoverableError = recoverableErrors[i];
+ var errorInfo = makeErrorInfo(recoverableError.stack);
+ onRecoverableError(recoverableError.value, errorInfo);
}
+ } // If the passive effects are the result of a discrete render, flush them
+ // synchronously at the end of the current task so that the result is
+ // immediately observable. Otherwise, we assume that they are not
+ // order-dependent and do not need to be observed by external systems, so we
+ // can wait until after paint.
+ // TODO: We can optimize this by not scheduling the callback earlier. Since we
+ // currently schedule the callback in multiple places, will wait until those
+ // are consolidated.
- markUpdate(workInProgress);
- }
-} // This function must be called at the very end of the complete phase, because
-// it might throw to suspend, and if the resource immediately loads, the work
-// loop will resume rendering as if the work-in-progress completed. So it must
-// fully complete.
-// TODO: This should ideally move to begin phase, but currently the instance is
-// not created until the complete phase. For our existing use cases, host nodes
-// that suspend don't have children, so it doesn't matter. But that might not
-// always be true in the future.
+ if (includesSyncLane(pendingPassiveEffectsLanes) && (disableLegacyMode )) {
+ flushPassiveEffects();
+ } // Read this again, since a passive effect might have updated it
-function preloadInstanceAndSuspendIfNeeded(workInProgress, type, props, renderLanes) {
- {
- // If this flag was set previously, we can remove it. The flag
- // represents whether this particular set of props might ever need to
- // suspend. The safest thing to do is for maySuspendCommit to always
- // return true, but if the renderer is reasonably confident that the
- // underlying resource won't be evicted, it can return false as a
- // performance optimization.
- workInProgress.flags &= ~MaySuspendCommit;
- return;
- } // Mark this fiber with a flag. This gets set on all host instances
-}
-function preloadResourceAndSuspendIfNeeded(workInProgress, resource, type, props, renderLanes) {
- // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
- if (!mayResourceSuspendCommit(resource)) {
- workInProgress.flags &= ~MaySuspendCommit;
- return;
- }
+ remainingLanes = root.pendingLanes; // Check if this render scheduled a cascading synchronous update. This is a
+ // heurstic to detect infinite update loops. We are intentionally excluding
+ // hydration lanes in this check, because render triggered by selective
+ // hydration is conceptually not an update.
- workInProgress.flags |= MaySuspendCommit;
- var rootRenderLanes = getWorkInProgressRootRenderLanes();
+ if ( // Check if there was a recursive update spawned by this render, in either
+ // the render phase or the commit phase. We track these explicitly because
+ // we can't infer from the remaining lanes alone.
+ (didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate) || // Was the finished render the result of an update (not hydration)?
+ includesSomeLane(lanes, UpdateLanes) && // Did it schedule a sync update?
+ includesSomeLane(remainingLanes, SyncUpdateLanes)) {
+ {
+ markNestedUpdateScheduled();
+ } // Count the number of times the root synchronously re-renders without
+ // finishing. If there are too many, it indicates an infinite update loop.
- if (!includesOnlyNonUrgentLanes(rootRenderLanes)) ; else {
- var isReady = preloadResource(resource);
- if (!isReady) {
- if (shouldRemainOnPreviousScreen()) {
- workInProgress.flags |= ShouldSuspendCommit;
- } else {
- suspendCommit();
- }
+ if (root === rootWithNestedUpdates) {
+ nestedUpdateCount++;
+ } else {
+ nestedUpdateCount = 0;
+ rootWithNestedUpdates = root;
}
- }
-}
+ } else {
+ nestedUpdateCount = 0;
+ } // If layout work was scheduled, flush it now.
-function scheduleRetryEffect(workInProgress, retryQueue) {
- var wakeables = retryQueue;
- if (wakeables !== null) {
- // Schedule an effect to attach a retry listener to the promise.
- // TODO: Move to passive phase
- workInProgress.flags |= Update;
- } else {
- // This boundary suspended, but no wakeables were added to the retry
- // queue. Check if the renderer suspended commit. If so, this means
- // that once the fallback is committed, we can immediately retry
- // rendering again, because rendering wasn't actually blocked. Only
- // the commit phase.
- // TODO: Consider a model where we always schedule an immediate retry, even
- // for normal Suspense. That way the retry can partially render up to the
- // first thing that suspends.
- if (workInProgress.flags & ScheduleRetry) {
- var retryLane = // TODO: This check should probably be moved into claimNextRetryLane
- // I also suspect that we need some further consolidation of offscreen
- // and retry lanes.
- workInProgress.tag !== OffscreenComponent ? claimNextRetryLane() : OffscreenLane;
- workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
- }
- }
-}
+ flushSyncWorkOnAllRoots();
-function updateHostText(current, workInProgress, oldText, newText) {
{
- // If the text differs, mark it as an update. All the work in done in commitWork.
- if (oldText !== newText) {
- markUpdate(workInProgress);
- }
+ markCommitStopped();
}
-}
-function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) {
- if (getIsHydrating()) {
- // If we're hydrating, we should consume as many items as we can
- // so we don't leave any behind.
- return;
- }
+ return null;
+}
- switch (renderState.tailMode) {
- case 'hidden':
- {
- // Any insertions at the end of the tail list after this point
- // should be invisible. If there are already mounted boundaries
- // anything before them are not considered for collapsing.
- // Therefore we need to go through the whole tail to find if
- // there are any.
- var tailNode = renderState.tail;
- var lastTailNode = null;
+function makeErrorInfo(componentStack) {
+ var errorInfo = {
+ componentStack: componentStack
+ };
- while (tailNode !== null) {
- if (tailNode.alternate !== null) {
- lastTailNode = tailNode;
- }
+ {
+ Object.defineProperty(errorInfo, 'digest', {
+ get: function () {
+ error('You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + ' This property is no longer provided as part of errorInfo but can be accessed as a property' + ' of the Error instance itself.');
+ }
+ });
+ }
- tailNode = tailNode.sibling;
- } // Next we're simply going to delete all insertions after the
- // last rendered item.
+ return errorInfo;
+}
+function releaseRootPooledCache(root, remainingLanes) {
+ {
+ var pooledCacheLanes = root.pooledCacheLanes &= remainingLanes;
- if (lastTailNode === null) {
- // All remaining items in the tail are insertions.
- renderState.tail = null;
- } else {
- // Detach the insertion after the last node that was already
- // inserted.
- lastTailNode.sibling = null;
- }
+ if (pooledCacheLanes === NoLanes) {
+ // None of the remaining work relies on the cache pool. Clear it so
+ // subsequent requests get a new cache
+ var pooledCache = root.pooledCache;
- break;
+ if (pooledCache != null) {
+ root.pooledCache = null;
+ releaseCache(pooledCache);
}
+ }
+ }
+}
- case 'collapsed':
- {
- // Any insertions at the end of the tail list after this point
- // should be invisible. If there are already mounted boundaries
- // anything before them are not considered for collapsing.
- // Therefore we need to go through the whole tail to find if
- // there are any.
- var _tailNode = renderState.tail;
- var _lastTailNode = null;
+function flushPassiveEffects() {
+ // Returns whether passive effects were flushed.
+ // TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should
+ // probably just combine the two functions. I believe they were only separate
+ // in the first place because we used to wrap it with
+ // `Scheduler.runWithPriority`, which accepts a function. But now we track the
+ // priority within React itself, so we can mutate the variable directly.
+ if (rootWithPendingPassiveEffects !== null) {
+ // Cache the root since rootWithPendingPassiveEffects is cleared in
+ // flushPassiveEffectsImpl
+ var root = rootWithPendingPassiveEffects; // Cache and clear the remaining lanes flag; it must be reset since this
+ // method can be called from various places, not always from commitRoot
+ // where the remaining lanes are known
- while (_tailNode !== null) {
- if (_tailNode.alternate !== null) {
- _lastTailNode = _tailNode;
- }
+ var remainingLanes = pendingPassiveEffectsRemainingLanes;
+ pendingPassiveEffectsRemainingLanes = NoLanes;
+ var renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
+ var priority = lowerEventPriority(DefaultEventPriority, renderPriority);
+ var prevTransition = ReactSharedInternals.T;
+ var previousPriority = getCurrentUpdatePriority();
- _tailNode = _tailNode.sibling;
- } // Next we're simply going to delete all insertions after the
- // last rendered item.
+ try {
+ setCurrentUpdatePriority(priority);
+ ReactSharedInternals.T = null;
+ return flushPassiveEffectsImpl();
+ } finally {
+ setCurrentUpdatePriority(previousPriority);
+ ReactSharedInternals.T = prevTransition; // Once passive effects have run for the tree - giving components a
+ // chance to retain cache instances they use - release the pooled
+ // cache at the root (if there is one)
+ releaseRootPooledCache(root, remainingLanes);
+ }
+ }
- if (_lastTailNode === null) {
- // All remaining items in the tail are insertions.
- if (!hasRenderedATailFallback && renderState.tail !== null) {
- // We suspended during the head. We want to show at least one
- // row at the tail. So we'll keep on and cut off the rest.
- renderState.tail.sibling = null;
- } else {
- renderState.tail = null;
- }
- } else {
- // Detach the insertion after the last node that was already
- // inserted.
- _lastTailNode.sibling = null;
- }
+ return false;
+}
+function enqueuePendingPassiveProfilerEffect(fiber) {
+ {
+ pendingPassiveProfilerEffects.push(fiber);
- break;
- }
+ if (!rootDoesHavePassiveEffects) {
+ rootDoesHavePassiveEffects = true;
+ scheduleCallback$1(NormalPriority$1, function () {
+ flushPassiveEffects();
+ return null;
+ });
+ }
}
}
-function bubbleProperties(completedWork) {
- var didBailout = completedWork.alternate !== null && completedWork.alternate.child === completedWork.child;
- var newChildLanes = NoLanes;
- var subtreeFlags = NoFlags$1;
+function flushPassiveEffectsImpl() {
+ if (rootWithPendingPassiveEffects === null) {
+ return false;
+ } // Cache and clear the transitions flag
- if (!didBailout) {
- // Bubble up the earliest expiration time.
- if ((completedWork.mode & ProfileMode) !== NoMode) {
- // In profiling mode, resetChildExpirationTime is also used to reset
- // profiler durations.
- var actualDuration = completedWork.actualDuration;
- var treeBaseDuration = completedWork.selfBaseDuration;
- var child = completedWork.child;
- while (child !== null) {
- newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes));
- subtreeFlags |= child.subtreeFlags;
- subtreeFlags |= child.flags; // When a fiber is cloned, its actualDuration is reset to 0. This value will
- // only be updated if work is done on the fiber (i.e. it doesn't bailout).
- // When work is done, it should bubble to the parent's actualDuration. If
- // the fiber has not been cloned though, (meaning no work was done), then
- // this value will reflect the amount of time spent working on a previous
- // render. In that case it should not bubble. We determine whether it was
- // cloned by comparing the child pointer.
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+ var transitions = pendingPassiveTransitions;
+ pendingPassiveTransitions = null;
+ var root = rootWithPendingPassiveEffects;
+ var lanes = pendingPassiveEffectsLanes;
+ rootWithPendingPassiveEffects = null; // TODO: This is sometimes out of sync with rootWithPendingPassiveEffects.
+ // Figure out why and fix it. It's not causing any known issues (probably
+ // because it's only used for profiling), but it's a refactor hazard.
- actualDuration += child.actualDuration; // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+ pendingPassiveEffectsLanes = NoLanes;
- treeBaseDuration += child.treeBaseDuration;
- child = child.sibling;
- }
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
+ throw new Error('Cannot flush passive effects while already rendering.');
+ }
- completedWork.actualDuration = actualDuration;
- completedWork.treeBaseDuration = treeBaseDuration;
- } else {
- var _child = completedWork.child;
+ {
+ isFlushingPassiveEffects = true;
+ didScheduleUpdateDuringPassiveEffects = false;
+ }
- while (_child !== null) {
- newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child.lanes, _child.childLanes));
- subtreeFlags |= _child.subtreeFlags;
- subtreeFlags |= _child.flags; // Update the return pointer so the tree is consistent. This is a code
- // smell because it assumes the commit phase is never concurrent with
- // the render phase. Will address during refactor to alternate model.
+ {
+ markPassiveEffectsStarted(lanes);
+ }
- _child.return = completedWork;
- _child = _child.sibling;
- }
+ var prevExecutionContext = executionContext;
+ executionContext |= CommitContext;
+ commitPassiveUnmountEffects(root.current);
+ commitPassiveMountEffects(root, root.current, lanes, transitions); // TODO: Move to commitPassiveMountEffects
+
+ {
+ var profilerEffects = pendingPassiveProfilerEffects;
+ pendingPassiveProfilerEffects = [];
+
+ for (var i = 0; i < profilerEffects.length; i++) {
+ var fiber = profilerEffects[i];
+ commitPassiveEffectDurations(root, fiber);
}
+ }
- completedWork.subtreeFlags |= subtreeFlags;
- } else {
- // Bubble up the earliest expiration time.
- if ((completedWork.mode & ProfileMode) !== NoMode) {
- // In profiling mode, resetChildExpirationTime is also used to reset
- // profiler durations.
- var _treeBaseDuration = completedWork.selfBaseDuration;
- var _child2 = completedWork.child;
+ {
+ markPassiveEffectsStopped();
+ }
- while (_child2 !== null) {
- newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child2.lanes, _child2.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to,
- // so we should bubble those up even during a bailout. All the other
- // flags have a lifetime only of a single render + commit, so we should
- // ignore them.
+ {
+ commitDoubleInvokeEffectsInDEV(root);
+ }
- subtreeFlags |= _child2.subtreeFlags & StaticMask;
- subtreeFlags |= _child2.flags & StaticMask; // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
+ executionContext = prevExecutionContext;
+ flushSyncWorkOnAllRoots();
- _treeBaseDuration += _child2.treeBaseDuration;
- _child2 = _child2.sibling;
+ {
+ // If additional passive effects were scheduled, increment a counter. If this
+ // exceeds the limit, we'll fire a warning.
+ if (didScheduleUpdateDuringPassiveEffects) {
+ if (root === rootWithPassiveNestedUpdates) {
+ nestedPassiveUpdateCount++;
+ } else {
+ nestedPassiveUpdateCount = 0;
+ rootWithPassiveNestedUpdates = root;
}
-
- completedWork.treeBaseDuration = _treeBaseDuration;
} else {
- var _child3 = completedWork.child;
+ nestedPassiveUpdateCount = 0;
+ }
- while (_child3 !== null) {
- newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child3.lanes, _child3.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to,
- // so we should bubble those up even during a bailout. All the other
- // flags have a lifetime only of a single render + commit, so we should
- // ignore them.
+ isFlushingPassiveEffects = false;
+ didScheduleUpdateDuringPassiveEffects = false;
+ } // TODO: Move to commitPassiveMountEffects
- subtreeFlags |= _child3.subtreeFlags & StaticMask;
- subtreeFlags |= _child3.flags & StaticMask; // Update the return pointer so the tree is consistent. This is a code
- // smell because it assumes the commit phase is never concurrent with
- // the render phase. Will address during refactor to alternate model.
- _child3.return = completedWork;
- _child3 = _child3.sibling;
- }
- }
+ onPostCommitRoot(root);
- completedWork.subtreeFlags |= subtreeFlags;
+ {
+ var stateNode = root.current.stateNode;
+ stateNode.effectDuration = 0;
+ stateNode.passiveEffectDuration = 0;
}
- completedWork.childLanes = newChildLanes;
- return didBailout;
+ return true;
}
-function completeDehydratedSuspenseBoundary(current, workInProgress, nextState) {
- var wasHydrated = popHydrationState(workInProgress);
+function isAlreadyFailedLegacyErrorBoundary(instance) {
+ return legacyErrorBoundariesThatAlreadyFailed !== null && legacyErrorBoundariesThatAlreadyFailed.has(instance);
+}
+function markLegacyErrorBoundaryAsFailed(instance) {
+ if (legacyErrorBoundariesThatAlreadyFailed === null) {
+ legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
+ } else {
+ legacyErrorBoundariesThatAlreadyFailed.add(instance);
+ }
+}
- if (nextState !== null && nextState.dehydrated !== null) {
- // We might be inside a hydration state the first time we're picking up this
- // Suspense boundary, and also after we've reentered it for further hydration.
- if (current === null) {
- if (!wasHydrated) {
- throw new Error('A dehydrated suspense component was completed without a hydrated node. ' + 'This is probably a bug in React.');
- }
+function captureCommitPhaseErrorOnRoot(rootFiber, sourceFiber, error) {
+ var errorInfo = createCapturedValueAtFiber(error, sourceFiber);
+ var update = createRootErrorUpdate(rootFiber.stateNode, errorInfo, SyncLane);
+ var root = enqueueUpdate(rootFiber, update, SyncLane);
- prepareToHydrateHostSuspenseInstance(workInProgress);
- bubbleProperties(workInProgress);
+ if (root !== null) {
+ markRootUpdated(root, SyncLane);
+ ensureRootIsScheduled(root);
+ }
+}
- {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- var isTimedOutSuspense = nextState !== null;
+function captureCommitPhaseError(sourceFiber, nearestMountedAncestor, error$1) {
+ {
+ setIsRunningInsertionEffect(false);
+ }
- if (isTimedOutSuspense) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- var primaryChildFragment = workInProgress.child;
+ if (sourceFiber.tag === HostRoot) {
+ // Error was thrown at the root. There is no parent, so the root
+ // itself should capture it.
+ captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error$1);
+ return;
+ }
- if (primaryChildFragment !== null) {
- // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration;
- }
- }
- }
- }
+ var fiber = nearestMountedAncestor;
- return false;
- } else {
- emitPendingHydrationWarnings(); // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration
- // state since we're now exiting out of it. popHydrationState doesn't do that for us.
+ while (fiber !== null) {
+ if (fiber.tag === HostRoot) {
+ captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error$1);
+ return;
+ } else if (fiber.tag === ClassComponent) {
+ var ctor = fiber.type;
+ var instance = fiber.stateNode;
- resetHydrationState();
+ if (typeof ctor.getDerivedStateFromError === 'function' || typeof instance.componentDidCatch === 'function' && !isAlreadyFailedLegacyErrorBoundary(instance)) {
+ var errorInfo = createCapturedValueAtFiber(error$1, sourceFiber);
+ var update = createClassErrorUpdate(SyncLane);
+ var root = enqueueUpdate(fiber, update, SyncLane);
- if ((workInProgress.flags & DidCapture) === NoFlags$1) {
- // This boundary did not suspend so it's now hydrated and unsuspended.
- workInProgress.memoizedState = null;
- } // If nothing suspended, we need to schedule an effect to mark this boundary
- // as having hydrated so events know that they're free to be invoked.
- // It's also a signal to replay events and the suspense callback.
- // If something suspended, schedule an effect to attach retry listeners.
- // So we might as well always mark this.
+ if (root !== null) {
+ initializeClassErrorUpdate(update, root, fiber, errorInfo);
+ markRootUpdated(root, SyncLane);
+ ensureRootIsScheduled(root);
+ }
+
+ return;
+ }
+ }
+ fiber = fiber.return;
+ }
- workInProgress.flags |= Update;
- bubbleProperties(workInProgress);
+ {
+ error('Internal React error: Attempted to capture a commit phase error ' + 'inside a detached tree. This indicates a bug in React. Potential ' + 'causes include deleting the same fiber more than once, committing an ' + 'already-finished tree, or an inconsistent return pointer.\n\n' + 'Error message:\n\n%s', error$1);
+ }
+}
+function attachPingListener(root, wakeable, lanes) {
+ // Attach a ping listener
+ //
+ // The data might resolve before we have a chance to commit the fallback. Or,
+ // in the case of a refresh, we'll never commit a fallback. So we need to
+ // attach a listener now. When it resolves ("pings"), we can decide whether to
+ // try rendering the tree again.
+ //
+ // Only attach a listener if one does not already exist for the lanes
+ // we're currently rendering (which acts like a "thread ID" here).
+ //
+ // We only need to do this in concurrent mode. Legacy Suspense always
+ // commits fallbacks synchronously, so there are no pings.
+ var pingCache = root.pingCache;
+ var threadIDs;
- {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- var _isTimedOutSuspense = nextState !== null;
+ if (pingCache === null) {
+ pingCache = root.pingCache = new PossiblyWeakMap();
+ threadIDs = new Set();
+ pingCache.set(wakeable, threadIDs);
+ } else {
+ threadIDs = pingCache.get(wakeable);
- if (_isTimedOutSuspense) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- var _primaryChildFragment = workInProgress.child;
+ if (threadIDs === undefined) {
+ threadIDs = new Set();
+ pingCache.set(wakeable, threadIDs);
+ }
+ }
- if (_primaryChildFragment !== null) {
- // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= _primaryChildFragment.treeBaseDuration;
- }
- }
- }
- }
+ if (!threadIDs.has(lanes)) {
+ workInProgressRootDidAttachPingListener = true; // Memoize using the thread ID to prevent redundant listeners.
- return false;
+ threadIDs.add(lanes);
+ var ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
+
+ {
+ if (isDevToolsPresent) {
+ // If we have pending work still, restore the original updaters
+ restorePendingUpdaters(root, lanes);
+ }
}
- } else {
- // Successfully completed this tree. If this was a forced client render,
- // there may have been recoverable errors during first hydration
- // attempt. If so, add them to a queue so we can log them in the
- // commit phase.
- upgradeHydrationErrorsToRecoverable(); // Fall through to normal Suspense path
- return true;
+ wakeable.then(ping, ping);
}
}
-function completeWork(current, workInProgress, renderLanes) {
- var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing
- // to the current tree provider fiber is just as fast and less error-prone.
- // Ideally we would have a special version of the work loop only
- // for hydration.
+function pingSuspendedRoot(root, wakeable, pingedLanes) {
+ var pingCache = root.pingCache;
- popTreeContext(workInProgress);
+ if (pingCache !== null) {
+ // The wakeable resolved, so we no longer need to memoize, because it will
+ // never be thrown again.
+ pingCache.delete(wakeable);
+ }
- switch (workInProgress.tag) {
- case IncompleteFunctionComponent:
- {
- {
- break;
- } // Fallthrough
+ markRootPinged(root, pingedLanes);
+ warnIfSuspenseResolutionNotWrappedWithActDEV();
+ if (workInProgressRoot === root && isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)) {
+ // Received a ping at the same priority level at which we're currently
+ // rendering. We might want to restart this render. This should mirror
+ // the logic of whether or not a root suspends once it completes.
+ // TODO: If we're rendering sync either due to Sync, Batched or expired,
+ // we should probably never restart.
+ // If we're suspended with delay, or if it's a retry, we'll always suspend
+ // so we can always restart.
+ if (workInProgressRootExitStatus === RootSuspendedWithDelay || workInProgressRootExitStatus === RootSuspended && includesOnlyRetries(workInProgressRootRenderLanes) && now$1() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) {
+ // Force a restart from the root by unwinding the stack. Unless this is
+ // being called from the render phase, because that would cause a crash.
+ if ((executionContext & RenderContext) === NoContext) {
+ prepareFreshStack(root, NoLanes);
}
+ } else {
+ // Even though we can't restart right now, we might get an
+ // opportunity later. So we mark this render as having a ping.
+ workInProgressRootPingedLanes = mergeLanes(workInProgressRootPingedLanes, pingedLanes);
+ }
+ }
- case LazyComponent:
- case SimpleMemoComponent:
- case FunctionComponent:
- case ForwardRef:
- case Fragment:
- case Mode:
- case Profiler:
- case ContextConsumer:
- case MemoComponent:
- bubbleProperties(workInProgress);
- return null;
+ ensureRootIsScheduled(root);
+}
- case ClassComponent:
- {
+function retryTimedOutBoundary(boundaryFiber, retryLane) {
+ // The boundary fiber (a Suspense component or SuspenseList component)
+ // previously was rendered in its fallback state. One of the promises that
+ // suspended it has resolved, which means at least part of the tree was
+ // likely unblocked. Try rendering again, at a new lanes.
+ if (retryLane === NoLane) {
+ // TODO: Assign this to `suspenseState.retryLane`? to avoid
+ // unnecessary entanglement?
+ retryLane = requestRetryLane();
+ } // TODO: Special case idle priority?
- bubbleProperties(workInProgress);
- return null;
- }
- case HostRoot:
- {
- var fiberRoot = workInProgress.stateNode;
+ var root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
- {
- var previousCache = null;
+ if (root !== null) {
+ markRootUpdated(root, retryLane);
+ ensureRootIsScheduled(root);
+ }
+}
- if (current !== null) {
- previousCache = current.memoizedState.cache;
- }
+function retryDehydratedSuspenseBoundary(boundaryFiber) {
+ var suspenseState = boundaryFiber.memoizedState;
+ var retryLane = NoLane;
- var cache = workInProgress.memoizedState.cache;
+ if (suspenseState !== null) {
+ retryLane = suspenseState.retryLane;
+ }
- if (cache !== previousCache) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive$1;
- }
+ retryTimedOutBoundary(boundaryFiber, retryLane);
+}
+function resolveRetryWakeable(boundaryFiber, wakeable) {
+ var retryLane = NoLane; // Default
- popCacheProvider(workInProgress);
- }
- popHostContainer(workInProgress);
+ var retryCache;
- if (fiberRoot.pendingContext) {
- fiberRoot.context = fiberRoot.pendingContext;
- fiberRoot.pendingContext = null;
- }
+ switch (boundaryFiber.tag) {
+ case SuspenseComponent:
+ retryCache = boundaryFiber.stateNode;
+ var suspenseState = boundaryFiber.memoizedState;
- if (current === null || current.child === null) {
- // If we hydrated, pop so that we can delete any remaining children
- // that weren't hydrated.
- var wasHydrated = popHydrationState(workInProgress);
+ if (suspenseState !== null) {
+ retryLane = suspenseState.retryLane;
+ }
- if (wasHydrated) {
- emitPendingHydrationWarnings(); // If we hydrated, then we'll need to schedule an update for
- // the commit side-effects on the root.
+ break;
- markUpdate(workInProgress);
- } else {
- if (current !== null) {
- var prevState = current.memoizedState;
+ case SuspenseListComponent:
+ retryCache = boundaryFiber.stateNode;
+ break;
- if ( // Check if this is a client root
- !prevState.isDehydrated || // Check if we reverted to client rendering (e.g. due to an error)
- (workInProgress.flags & ForceClientRender) !== NoFlags$1) {
- // Schedule an effect to clear this container at the start of the
- // next commit. This handles the case of React rendering into a
- // container with previous children. It's also safe to do for
- // updates too, because current.child would only be null if the
- // previous render was null (so the container would already
- // be empty).
- workInProgress.flags |= Snapshot; // If this was a forced client render, there may have been
- // recoverable errors during first hydration attempt. If so, add
- // them to a queue so we can log them in the commit phase.
+ case OffscreenComponent:
+ {
+ var instance = boundaryFiber.stateNode;
+ retryCache = instance._retryCache;
+ break;
+ }
- upgradeHydrationErrorsToRecoverable();
- }
- }
- }
- }
- bubbleProperties(workInProgress);
+ default:
+ throw new Error('Pinged unknown suspense boundary type. ' + 'This is probably a bug in React.');
+ }
- return null;
+ if (retryCache !== null) {
+ // The wakeable resolved, so we no longer need to memoize, because it will
+ // never be thrown again.
+ retryCache.delete(wakeable);
+ }
+
+ retryTimedOutBoundary(boundaryFiber, retryLane);
+}
+function throwIfInfiniteUpdateLoopDetected() {
+ if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
+ nestedUpdateCount = 0;
+ nestedPassiveUpdateCount = 0;
+ rootWithNestedUpdates = null;
+ rootWithPassiveNestedUpdates = null;
+
+ {
+ if (executionContext & RenderContext && workInProgressRoot !== null) {
+ // We're in the render phase. Disable the concurrent error recovery
+ // mechanism to ensure that the error we're about to throw gets handled.
+ // We need it to trigger the nearest error boundary so that the infinite
+ // update loop is broken.
+ workInProgressRoot.errorRecoveryDisabledLanes = mergeLanes(workInProgressRoot.errorRecoveryDisabledLanes, workInProgressRootRenderLanes);
}
+ }
- case HostHoistable:
- {
- {
- var nextResource = workInProgress.memoizedState;
+ throw new Error('Maximum update depth exceeded. This can happen when a component ' + 'repeatedly calls setState inside componentWillUpdate or ' + 'componentDidUpdate. React limits the number of nested updates to ' + 'prevent infinite loops.');
+ }
+
+ {
+ if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
+ nestedPassiveUpdateCount = 0;
+ rootWithPassiveNestedUpdates = null;
- if (current === null) {
- // We are mounting and must Update this Hoistable in this commit
- // @TODO refactor this block to create the instance here in complete
- // phase if we are not hydrating.
- markUpdate(workInProgress);
+ error('Maximum update depth exceeded. This can happen when a component ' + "calls setState inside useEffect, but useEffect either doesn't " + 'have a dependency array, or one of the dependencies changes on ' + 'every render.');
+ }
+ }
+}
- if (nextResource !== null) {
- // This is a Hoistable Resource
- // This must come at the very end of the complete phase.
- bubbleProperties(workInProgress);
- preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
- return null;
- } else {
- // This is a Hoistable Instance
- // This must come at the very end of the complete phase.
- bubbleProperties(workInProgress);
- preloadInstanceAndSuspendIfNeeded(workInProgress);
- return null;
- }
- } else {
- // We are updating.
- var currentResource = current.memoizedState;
+function flushRenderPhaseStrictModeWarningsInDEV() {
+ {
+ ReactStrictModeWarnings.flushLegacyContextWarning();
+ ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
+ }
+}
- if (nextResource !== currentResource) {
- // We are transitioning to, from, or between Hoistable Resources
- // and require an update
- markUpdate(workInProgress);
- }
+function recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, parentFiber, isInStrictMode) {
+ if ((parentFiber.subtreeFlags & (PlacementDEV | Visibility)) === NoFlags$1) {
+ // Parent's descendants have already had effects double invoked.
+ // Early exit to avoid unnecessary tree traversal.
+ return;
+ }
- if (nextResource !== null) {
- // This is a Hoistable Resource
- // This must come at the very end of the complete phase.
- bubbleProperties(workInProgress);
+ var child = parentFiber.child;
- if (nextResource === currentResource) {
- workInProgress.flags &= ~MaySuspendCommit;
- } else {
- preloadResourceAndSuspendIfNeeded(workInProgress, nextResource);
- }
+ while (child !== null) {
+ doubleInvokeEffectsInDEVIfNecessary(root, child, isInStrictMode);
+ child = child.sibling;
+ }
+} // Unconditionally disconnects and connects passive and layout effects.
- return null;
- } else {
- // This is a Hoistable Instance
- // We may have props to update on the Hoistable instance.
- {
- var oldProps = current.memoizedProps;
- if (oldProps !== newProps) {
- markUpdate(workInProgress);
- }
- } // This must come at the very end of the complete phase.
+function doubleInvokeEffectsOnFiber(root, fiber) {
+ var shouldDoubleInvokePassiveEffects = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
+ disappearLayoutEffects(fiber);
+ if (shouldDoubleInvokePassiveEffects) {
+ disconnectPassiveEffect(fiber);
+ }
- bubbleProperties(workInProgress);
- preloadInstanceAndSuspendIfNeeded(workInProgress);
- return null;
- }
- }
- } // Fall through
+ reappearLayoutEffects(root, fiber.alternate, fiber, false);
- }
+ if (shouldDoubleInvokePassiveEffects) {
+ reconnectPassiveEffects(root, fiber, NoLanes, null, false);
+ }
+}
- case HostSingleton:
- {
- {
- popHostContext(workInProgress);
- var rootContainerInstance = getRootHostContainer();
- var _type = workInProgress.type;
+function doubleInvokeEffectsInDEVIfNecessary(root, fiber, parentIsInStrictMode) {
+ var isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
+ var isInStrictMode = parentIsInStrictMode || isStrictModeFiber; // First case: the fiber **is not** of type OffscreenComponent. No
+ // special rules apply to double invoking effects.
- if (current !== null && workInProgress.stateNode != null) {
- {
- var _oldProps2 = current.memoizedProps;
+ if (fiber.tag !== OffscreenComponent) {
+ if (fiber.flags & PlacementDEV) {
+ setCurrentFiber(fiber);
- if (_oldProps2 !== newProps) {
- markUpdate(workInProgress);
- }
- }
- } else {
- if (!newProps) {
- if (workInProgress.stateNode === null) {
- throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
- } // This can happen when we abort work.
+ if (isInStrictMode) {
+ doubleInvokeEffectsOnFiber(root, fiber, (fiber.mode & NoStrictPassiveEffectsMode) === NoMode);
+ }
+ resetCurrentFiber();
+ } else {
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
+ }
- bubbleProperties(workInProgress);
- return null;
- }
+ return;
+ } // Second case: the fiber **is** of type OffscreenComponent.
+ // This branch contains cases specific to Offscreen.
- var currentHostContext = getHostContext();
- var _wasHydrated = popHydrationState(workInProgress);
+ if (fiber.memoizedState === null) {
+ // Only consider Offscreen that is visible.
+ // TODO (Offscreen) Handle manual mode.
+ setCurrentFiber(fiber);
- var instance;
+ if (isInStrictMode && fiber.flags & Visibility) {
+ // Double invoke effects on Offscreen's subtree only
+ // if it is visible and its visibility has changed.
+ doubleInvokeEffectsOnFiber(root, fiber);
+ } else if (fiber.subtreeFlags & PlacementDEV) {
+ // Something in the subtree could have been suspended.
+ // We need to continue traversal and find newly inserted fibers.
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, fiber, isInStrictMode);
+ }
- if (_wasHydrated) {
- // We ignore the boolean indicating there is an updateQueue because
- // it is used only to set text children and HostSingletons do not
- // use them.
- prepareToHydrateHostInstance(workInProgress, currentHostContext);
- instance = workInProgress.stateNode;
- } else {
- instance = resolveSingletonInstance(_type, newProps, rootContainerInstance, currentHostContext, true);
- workInProgress.stateNode = instance;
- markUpdate(workInProgress);
- }
- }
+ resetCurrentFiber();
+ }
+}
- bubbleProperties(workInProgress);
- return null;
- } // Fall through
+function commitDoubleInvokeEffectsInDEV(root, hasPassiveEffects) {
+ {
+ {
+ var doubleInvokeEffects = true;
+ if (!(root.current.mode & (StrictLegacyMode | StrictEffectsMode))) {
+ doubleInvokeEffects = false;
}
- case HostComponent:
- {
- popHostContext(workInProgress);
- var _type2 = workInProgress.type;
+ recursivelyTraverseAndDoubleInvokeEffectsInDEV(root, root.current, doubleInvokeEffects);
+ }
+ }
+}
- if (current !== null && workInProgress.stateNode != null) {
- updateHostComponent(current, workInProgress, _type2, newProps);
- } else {
- if (!newProps) {
- if (workInProgress.stateNode === null) {
- throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
- } // This can happen when we abort work.
+var didWarnStateUpdateForNotYetMountedComponent = null;
+function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
+ {
+ if ((executionContext & RenderContext) !== NoContext) {
+ // We let the other warning about render phase updates deal with this one.
+ return;
+ }
+ var tag = fiber.tag;
- bubbleProperties(workInProgress);
- return null;
- }
+ if (tag !== HostRoot && tag !== ClassComponent && tag !== FunctionComponent && tag !== ForwardRef && tag !== MemoComponent && tag !== SimpleMemoComponent) {
+ // Only warn for user-defined components, not internal ones like Suspense.
+ return;
+ } // We show the whole stack but dedupe on the top component's name because
+ // the problematic code almost always lies inside that component.
- var _currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
- // "stack" as the parent. Then append children as we go in beginWork
- // or completeWork depending on whether we want to add them top->down or
- // bottom->up. Top->down is faster in IE11.
+ var componentName = getComponentNameFromFiber(fiber) || 'ReactComponent';
- var _wasHydrated2 = popHydrationState(workInProgress);
+ if (didWarnStateUpdateForNotYetMountedComponent !== null) {
+ if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) {
+ return;
+ } // $FlowFixMe[incompatible-use] found when upgrading Flow
- if (_wasHydrated2) {
- // TODO: Move this and createInstance step into the beginPhase
- // to consolidate.
- prepareToHydrateHostInstance(workInProgress, _currentHostContext);
- } else {
- var _rootContainerInstance = getRootHostContainer();
- var _instance3 = createInstance(_type2, newProps, _rootContainerInstance, _currentHostContext, workInProgress); // TODO: For persistent renderers, we should pass children as part
- // of the initial instance creation
+ didWarnStateUpdateForNotYetMountedComponent.add(componentName);
+ } else {
+ didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]);
+ }
+ var previousFiber = current;
- appendAllChildren(_instance3, workInProgress);
- workInProgress.stateNode = _instance3; // Certain renderers require commit-time effects for initial mount.
- // (eg DOM renderer supports auto-focus for certain elements).
- // Make sure such renderers get scheduled for later work.
+ try {
+ setCurrentFiber(fiber);
- if (finalizeInitialChildren(_instance3, _type2, newProps)) {
- markUpdate(workInProgress);
- }
- }
- }
+ error("Can't perform a React state update on a component that hasn't mounted yet. " + 'This indicates that you have a side-effect in your render function that ' + 'asynchronously later calls tries to update the component. Move this work to ' + 'useEffect instead.');
+ } finally {
+ if (previousFiber) {
+ setCurrentFiber(fiber);
+ } else {
+ resetCurrentFiber();
+ }
+ }
+ }
+}
+var didWarnAboutUpdateInRender = false;
+var didWarnAboutUpdateInRenderForAnotherComponent;
- bubbleProperties(workInProgress); // This must come at the very end of the complete phase, because it might
- // throw to suspend, and if the resource immediately loads, the work loop
- // will resume rendering as if the work-in-progress completed. So it must
- // fully complete.
+{
+ didWarnAboutUpdateInRenderForAnotherComponent = new Set();
+}
- preloadInstanceAndSuspendIfNeeded(workInProgress);
- return null;
- }
+function warnAboutRenderPhaseUpdatesInDEV(fiber) {
+ {
+ if (isRendering) {
+ switch (fiber.tag) {
+ case FunctionComponent:
+ case ForwardRef:
+ case SimpleMemoComponent:
+ {
+ var renderingComponentName = workInProgress && getComponentNameFromFiber(workInProgress) || 'Unknown'; // Dedupe by the rendering component because it's the one that needs to be fixed.
- case HostText:
- {
- var newText = newProps;
+ var dedupeKey = renderingComponentName;
- if (current && workInProgress.stateNode != null) {
- var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
- // to schedule a side-effect to do the updates.
+ if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
+ didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
+ var setStateComponentName = getComponentNameFromFiber(fiber) || 'Unknown';
- updateHostText(current, workInProgress, oldText, newText);
- } else {
- if (typeof newText !== 'string') {
- if (workInProgress.stateNode === null) {
- throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
- } // This can happen when we abort work.
+ error('Cannot update a component (`%s`) while rendering a ' + 'different component (`%s`). To locate the bad setState() call inside `%s`, ' + 'follow the stack trace as described in https://react.dev/link/setstate-in-render', setStateComponentName, renderingComponentName, renderingComponentName);
+ }
+ break;
}
- var _rootContainerInstance2 = getRootHostContainer();
-
- var _currentHostContext2 = getHostContext();
+ case ClassComponent:
+ {
+ if (!didWarnAboutUpdateInRender) {
+ error('Cannot update during an existing state transition (such as ' + 'within `render`). Render methods should be a pure ' + 'function of props and state.');
- var _wasHydrated3 = popHydrationState(workInProgress);
+ didWarnAboutUpdateInRender = true;
+ }
- if (_wasHydrated3) {
- prepareToHydrateHostTextInstance(workInProgress);
- } else {
- workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance2, _currentHostContext2, workInProgress);
+ break;
}
- }
-
- bubbleProperties(workInProgress);
- return null;
}
+ }
+ }
+}
- case SuspenseComponent:
- {
- var nextState = workInProgress.memoizedState; // Special path for dehydrated boundaries. We may eventually move this
- // to its own fiber type so that we can add other kinds of hydration
- // boundaries that aren't associated with a Suspense tree. In anticipation
- // of such a refactor, all the hydration logic is contained in
- // this branch.
-
- if (current === null || current.memoizedState !== null && current.memoizedState.dehydrated !== null) {
- var fallthroughToNormalSuspensePath = completeDehydratedSuspenseBoundary(current, workInProgress, nextState);
+function restorePendingUpdaters(root, lanes) {
+ {
+ if (isDevToolsPresent) {
+ var memoizedUpdaters = root.memoizedUpdaters;
+ memoizedUpdaters.forEach(function (schedulingFiber) {
+ addFiberToLanesMap(root, schedulingFiber, lanes);
+ }); // This function intentionally does not clear memoized updaters.
+ // Those may still be relevant to the current commit
+ // and a future one (e.g. Suspense).
+ }
+ }
+}
+var fakeActCallbackNode$1 = {}; // $FlowFixMe[missing-local-annot]
- if (!fallthroughToNormalSuspensePath) {
- if (workInProgress.flags & ForceClientRender) {
- popSuspenseHandler(workInProgress); // Special case. There were remaining unhydrated nodes. We treat
- // this as a mismatch. Revert to client rendering.
+function scheduleCallback$1(priorityLevel, callback) {
+ {
+ // If we're currently inside an `act` scope, bypass Scheduler and push to
+ // the `act` queue instead.
+ var actQueue = ReactSharedInternals.actQueue;
- return workInProgress;
- } else {
- popSuspenseHandler(workInProgress); // Did not finish hydrating, either because this is the initial
- // render or because something suspended.
+ if (actQueue !== null) {
+ actQueue.push(callback);
+ return fakeActCallbackNode$1;
+ } else {
+ return scheduleCallback$3(priorityLevel, callback);
+ }
+ }
+}
- return null;
- }
- } // Continue with the normal Suspense path.
+function shouldForceFlushFallbacksInDEV() {
+ // Never force flush in production. This function should get stripped out.
+ return ReactSharedInternals.actQueue !== null;
+}
- }
+function warnIfUpdatesNotWrappedWithActDEV(fiber) {
+ {
+ {
+ if (!isConcurrentActEnvironment()) {
+ // Not in an act environment. No need to warn.
+ return;
+ }
+ }
- popSuspenseHandler(workInProgress);
+ if (ReactSharedInternals.actQueue === null) {
+ var previousFiber = current;
- if ((workInProgress.flags & DidCapture) !== NoFlags$1) {
- // Something suspended. Re-render with the fallback children.
- workInProgress.lanes = renderLanes; // Do not reset the effect list.
+ try {
+ setCurrentFiber(fiber);
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- transferActualDuration(workInProgress);
- } // Don't bubble properties in this case.
+ error('An update to %s inside a test was not wrapped in act(...).\n\n' + 'When testing, code that causes React state updates should be ' + 'wrapped into act(...):\n\n' + 'act(() => {\n' + ' /* fire events that update state */\n' + '});\n' + '/* assert on the output */\n\n' + "This ensures that you're testing the behavior the user would see " + 'in the browser.' + ' Learn more at https://react.dev/link/wrap-tests-with-act', getComponentNameFromFiber(fiber));
+ } finally {
+ if (previousFiber) {
+ setCurrentFiber(fiber);
+ } else {
+ resetCurrentFiber();
+ }
+ }
+ }
+ }
+}
+function warnIfSuspenseResolutionNotWrappedWithActDEV(root) {
+ {
+ if (isConcurrentActEnvironment() && ReactSharedInternals.actQueue === null) {
+ error('A suspended resource finished loading inside a test, but the event ' + 'was not wrapped in act(...).\n\n' + 'When testing, code that resolves suspended data should be wrapped ' + 'into act(...):\n\n' + 'act(() => {\n' + ' /* finish loading suspended data */\n' + '});\n' + '/* assert on the output */\n\n' + "This ensures that you're testing the behavior the user would see " + 'in the browser.' + ' Learn more at https://react.dev/link/wrap-tests-with-act');
+ }
+ }
+}
- return workInProgress;
- }
+function setIsRunningInsertionEffect(isRunning) {
+ {
+ isRunningInsertionEffect = isRunning;
+ }
+}
- var nextDidTimeout = nextState !== null;
- var prevDidTimeout = current !== null && current.memoizedState !== null;
+// there's only a single root, but we do support multi root apps, hence this
+// extra complexity. But this module is optimized for the single root case.
- if (nextDidTimeout) {
- var offscreenFiber = workInProgress.child;
- var _previousCache = null;
+var firstScheduledRoot = null;
+var lastScheduledRoot = null; // Used to prevent redundant mircotasks from being scheduled.
- if (offscreenFiber.alternate !== null && offscreenFiber.alternate.memoizedState !== null && offscreenFiber.alternate.memoizedState.cachePool !== null) {
- _previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;
- }
+var didScheduleMicrotask = false; // `act` "microtasks" are scheduled on the `act` queue instead of an actual
+// microtask, so we have to dedupe those separately. This wouldn't be an issue
+// if we required all `act` calls to be awaited, which we might in the future.
- var _cache = null;
+var didScheduleMicrotask_act = false; // Used to quickly bail out of flushSync if there's no sync work to do.
- if (offscreenFiber.memoizedState !== null && offscreenFiber.memoizedState.cachePool !== null) {
- _cache = offscreenFiber.memoizedState.cachePool.pool;
- }
+var mightHavePendingSyncWork = false;
+var isFlushingWork = false;
+var currentEventTransitionLane = NoLane;
+function ensureRootIsScheduled(root) {
+ // This function is called whenever a root receives an update. It does two
+ // things 1) it ensures the root is in the root schedule, and 2) it ensures
+ // there's a pending microtask to process the root schedule.
+ //
+ // Most of the actual scheduling logic does not happen until
+ // `scheduleTaskForRootDuringMicrotask` runs.
+ // Add the root to the schedule
+ if (root === lastScheduledRoot || root.next !== null) ; else {
+ if (lastScheduledRoot === null) {
+ firstScheduledRoot = lastScheduledRoot = root;
+ } else {
+ lastScheduledRoot.next = root;
+ lastScheduledRoot = root;
+ }
+ } // Any time a root received an update, we set this to true until the next time
+ // we process the schedule. If it's false, then we can quickly exit flushSync
+ // without consulting the schedule.
- if (_cache !== _previousCache) {
- // Run passive effects to retain/release the cache.
- offscreenFiber.flags |= Passive$1;
- }
- } // If the suspended state of the boundary changes, we need to schedule
- // a passive effect, which is when we process the transitions
+ mightHavePendingSyncWork = true; // At the end of the current event, go through each of the roots and ensure
+ // there's a task scheduled for each one at the correct priority.
- if (nextDidTimeout !== prevDidTimeout) {
- // an effect to toggle the subtree's visibility. When we switch from
- // fallback -> primary, the inner Offscreen fiber schedules this effect
- // as part of its normal complete phase. But when we switch from
- // primary -> fallback, the inner Offscreen fiber does not have a complete
- // phase. So we need to schedule its effect here.
- //
- // We also use this flag to connect/disconnect the effects, but the same
- // logic applies: when re-connecting, the Offscreen fiber's complete
- // phase will handle scheduling the effect. It's only when the fallback
- // is active that we have to do anything special.
+ if (ReactSharedInternals.actQueue !== null) {
+ // We're inside an `act` scope.
+ if (!didScheduleMicrotask_act) {
+ didScheduleMicrotask_act = true;
+ scheduleImmediateTask(processRootScheduleInMicrotask);
+ }
+ } else {
+ if (!didScheduleMicrotask) {
+ didScheduleMicrotask = true;
+ scheduleImmediateTask(processRootScheduleInMicrotask);
+ }
+ }
+}
+function flushSyncWorkOnAllRoots() {
+ // This is allowed to be called synchronously, but the caller should check
+ // the execution context first.
+ flushSyncWorkAcrossRoots_impl(false);
+}
+function flushSyncWorkAcrossRoots_impl(onlyLegacy) {
+ if (isFlushingWork) {
+ // Prevent reentrancy.
+ // TODO: Is this overly defensive? The callers must check the execution
+ // context first regardless.
+ return;
+ }
- if (nextDidTimeout) {
- var _offscreenFiber2 = workInProgress.child;
- _offscreenFiber2.flags |= Visibility;
- }
- }
+ if (!mightHavePendingSyncWork) {
+ // Fast path. There's no sync work to do.
+ return;
+ } // There may or may not be synchronous work scheduled. Let's check.
- var retryQueue = workInProgress.updateQueue;
- scheduleRetryEffect(workInProgress, retryQueue);
- bubbleProperties(workInProgress);
+ var didPerformSomeWork;
+ isFlushingWork = true;
- {
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- if (nextDidTimeout) {
- // Don't count time spent in a timed out Suspense subtree as part of the base duration.
- var primaryChildFragment = workInProgress.child;
+ do {
+ didPerformSomeWork = false;
+ var root = firstScheduledRoot;
- if (primaryChildFragment !== null) {
- // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator
- workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration;
- }
- }
- }
- }
+ while (root !== null) {
+ if (onlyLegacy && (disableLegacyMode )) ; else {
+ var workInProgressRoot = getWorkInProgressRoot();
+ var workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
+ var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
- return null;
+ if (includesSyncLane(nextLanes)) {
+ // This root has pending sync work. Flush it now.
+ didPerformSomeWork = true;
+ performSyncWorkOnRoot(root, nextLanes);
+ }
}
- case HostPortal:
- popHostContainer(workInProgress);
+ root = root.next;
+ }
+ } while (didPerformSomeWork);
- if (current === null) {
- preparePortalMount(workInProgress.stateNode.containerInfo);
- }
+ isFlushingWork = false;
+}
- bubbleProperties(workInProgress);
- return null;
+function processRootScheduleInMicrotask() {
+ // This function is always called inside a microtask. It should never be
+ // called synchronously.
+ didScheduleMicrotask = false;
- case ContextProvider:
- // Pop provider fiber
- var context;
+ {
+ didScheduleMicrotask_act = false;
+ } // We'll recompute this as we iterate through all the roots and schedule them.
- {
- context = workInProgress.type;
- }
- popProvider(context, workInProgress);
- bubbleProperties(workInProgress);
- return null;
+ mightHavePendingSyncWork = false;
+ var currentTime = now$1();
+ var prev = null;
+ var root = firstScheduledRoot;
- case IncompleteClassComponent:
- {
- {
- break;
- } // Same as class component case. I put it down here so that the tags are
- }
+ while (root !== null) {
+ var next = root.next;
- case SuspenseListComponent:
- {
- popSuspenseListContext(workInProgress);
- var renderState = workInProgress.memoizedState;
+ if (currentEventTransitionLane !== NoLane && shouldAttemptEagerTransition()) {
+ // A transition was scheduled during an event, but we're going to try to
+ // render it synchronously anyway. We do this during a popstate event to
+ // preserve the scroll position of the previous page.
+ upgradePendingLaneToSync(root, currentEventTransitionLane);
+ }
- if (renderState === null) {
- // We're running in the default, "independent" mode.
- // We don't do anything in this mode.
- bubbleProperties(workInProgress);
- return null;
- }
+ var nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);
- var didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags$1;
- var renderedTail = renderState.rendering;
+ if (nextLanes === NoLane) {
+ // This root has no more pending work. Remove it from the schedule. To
+ // guard against subtle reentrancy bugs, this microtask is the only place
+ // we do this — you can add roots to the schedule whenever, but you can
+ // only remove them here.
+ // Null this out so we know it's been removed from the schedule.
+ root.next = null;
- if (renderedTail === null) {
- // We just rendered the head.
- if (!didSuspendAlready) {
- // This is the first pass. We need to figure out if anything is still
- // suspended in the rendered set.
- // If new content unsuspended, but there's still some content that
- // didn't. Then we need to do a second pass that forces everything
- // to keep showing their fallbacks.
- // We might be suspended if something in this render pass suspended, or
- // something in the previous committed pass suspended. Otherwise,
- // there's no chance so we can skip the expensive call to
- // findFirstSuspended.
- var cannotBeSuspended = renderHasNotSuspendedYet() && (current === null || (current.flags & DidCapture) === NoFlags$1);
+ if (prev === null) {
+ // This is the new head of the list
+ firstScheduledRoot = next;
+ } else {
+ prev.next = next;
+ }
- if (!cannotBeSuspended) {
- var row = workInProgress.child;
+ if (next === null) {
+ // This is the new tail of the list
+ lastScheduledRoot = prev;
+ }
+ } else {
+ // This root still has work. Keep it in the list.
+ prev = root;
- while (row !== null) {
- var suspended = findFirstSuspended(row);
+ if (includesSyncLane(nextLanes)) {
+ mightHavePendingSyncWork = true;
+ }
+ }
- if (suspended !== null) {
- didSuspendAlready = true;
- workInProgress.flags |= DidCapture;
- cutOffTailIfNeeded(renderState, false); // If this is a newly suspended tree, it might not get committed as
- // part of the second pass. In that case nothing will subscribe to
- // its thenables. Instead, we'll transfer its thenables to the
- // SuspenseList so that it can retry if they resolve.
- // There might be multiple of these in the list but since we're
- // going to wait for all of them anyway, it doesn't really matter
- // which ones gets to ping. In theory we could get clever and keep
- // track of how many dependencies remain but it gets tricky because
- // in the meantime, we can add/remove/change items and dependencies.
- // We might bail out of the loop before finding any but that
- // doesn't matter since that means that the other boundaries that
- // we did find already has their listeners attached.
+ root = next;
+ }
- var _retryQueue = suspended.updateQueue;
- workInProgress.updateQueue = _retryQueue;
- scheduleRetryEffect(workInProgress, _retryQueue); // Rerender the whole list, but this time, we'll force fallbacks
- // to stay in place.
- // Reset the effect flags before doing the second pass since that's now invalid.
- // Reset the child fibers to their original state.
+ currentEventTransitionLane = NoLane; // At the end of the microtask, flush any pending synchronous work. This has
+ // to come at the end, because it does actual rendering work that might throw.
- workInProgress.subtreeFlags = NoFlags$1;
- resetChildFibers(workInProgress, renderLanes); // Set up the Suspense List Context to force suspense and
- // immediately rerender the children.
+ flushSyncWorkOnAllRoots();
+}
- pushSuspenseListContext(workInProgress, setShallowSuspenseListContext(suspenseStackCursor.current, ForceSuspenseFallback)); // Don't bubble properties in this case.
+function scheduleTaskForRootDuringMicrotask(root, currentTime) {
+ // This function is always called inside a microtask, or at the very end of a
+ // rendering task right before we yield to the main thread. It should never be
+ // called synchronously.
+ //
+ // TODO: Unless enableDeferRootSchedulingToMicrotask is off. We need to land
+ // that ASAP to unblock additional features we have planned.
+ //
+ // This function also never performs React work synchronously; it should
+ // only schedule work to be performed later, in a separate task or microtask.
+ // Check if any lanes are being starved by other work. If so, mark them as
+ // expired so we know to work on those next.
+ markStarvedLanesAsExpired(root, currentTime); // Determine the next lanes to work on, and their priority.
- return workInProgress.child;
- }
+ var workInProgressRoot = getWorkInProgressRoot();
+ var workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
+ var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
+ var existingCallbackNode = root.callbackNode;
- row = row.sibling;
- }
- }
+ if ( // Check if there's nothing to work on
+ nextLanes === NoLanes || // If this root is currently suspended and waiting for data to resolve, don't
+ // schedule a task to render it. We'll either wait for a ping, or wait to
+ // receive an update.
+ //
+ // Suspended render phase
+ root === workInProgressRoot && isWorkLoopSuspendedOnData() || // Suspended commit phase
+ root.cancelPendingCommit !== null) {
+ // Fast path: There's nothing to work on.
+ if (existingCallbackNode !== null) {
+ cancelCallback(existingCallbackNode);
+ }
- if (renderState.tail !== null && now$1() > getRenderTargetTime()) {
- // We have already passed our CPU deadline but we still have rows
- // left in the tail. We'll just give up further attempts to render
- // the main content and only render fallbacks.
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true;
- cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
- // to get it started back up to attempt the next item. While in terms
- // of priority this work has the same priority as this current render,
- // it's not part of the same transition once the transition has
- // committed. If it's sync, we still want to yield so that it can be
- // painted. Conceptually, this is really the same as pinging.
- // We can use any RetryLane even if it's the one currently rendering
- // since we're leaving it behind on this node.
+ root.callbackNode = null;
+ root.callbackPriority = NoLane;
+ return NoLane;
+ } // Schedule a new callback in the host environment.
- workInProgress.lanes = SomeRetryLane;
- }
- } else {
- cutOffTailIfNeeded(renderState, false);
- } // Next we're going to render the tail.
- } else {
- // Append the rendered row to the child list.
- if (!didSuspendAlready) {
- var _suspended = findFirstSuspended(renderedTail);
+ if (includesSyncLane(nextLanes)) {
+ // Synchronous work is always flushed at the end of the microtask, so we
+ // don't need to schedule an additional task.
+ if (existingCallbackNode !== null) {
+ cancelCallback(existingCallbackNode);
+ }
- if (_suspended !== null) {
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true; // Ensure we transfer the update queue to the parent so that it doesn't
- // get lost if this row ends up dropped during a second pass.
+ root.callbackPriority = SyncLane;
+ root.callbackNode = null;
+ return SyncLane;
+ } else {
+ // We use the highest priority lane to represent the priority of the callback.
+ var existingCallbackPriority = root.callbackPriority;
+ var newCallbackPriority = getHighestPriorityLane(nextLanes);
- var _retryQueue2 = _suspended.updateQueue;
- workInProgress.updateQueue = _retryQueue2;
- scheduleRetryEffect(workInProgress, _retryQueue2);
- cutOffTailIfNeeded(renderState, true); // This might have been modified.
+ if (newCallbackPriority === existingCallbackPriority && // Special case related to `act`. If the currently scheduled task is a
+ // Scheduler task, rather than an `act` task, cancel it and re-schedule
+ // on the `act` queue.
+ !(ReactSharedInternals.actQueue !== null && existingCallbackNode !== fakeActCallbackNode)) {
+ // The priority hasn't changed. We can reuse the existing task.
+ return newCallbackPriority;
+ } else {
+ // Cancel the existing callback. We'll schedule a new one below.
+ cancelCallback(existingCallbackNode);
+ }
- if (renderState.tail === null && renderState.tailMode === 'hidden' && !renderedTail.alternate && !getIsHydrating() // We don't cut it if we're hydrating.
- ) {
- // We're done.
- bubbleProperties(workInProgress);
- return null;
- }
- } else if ( // The time it took to render last row is greater than the remaining
- // time we have to render. So rendering one more row would likely
- // exceed it.
- now$1() * 2 - renderState.renderingStartTime > getRenderTargetTime() && renderLanes !== OffscreenLane) {
- // We have now passed our CPU deadline and we'll just give up further
- // attempts to render the main content and only render fallbacks.
- // The assumption is that this is usually faster.
- workInProgress.flags |= DidCapture;
- didSuspendAlready = true;
- cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
- // to get it started back up to attempt the next item. While in terms
- // of priority this work has the same priority as this current render,
- // it's not part of the same transition once the transition has
- // committed. If it's sync, we still want to yield so that it can be
- // painted. Conceptually, this is really the same as pinging.
- // We can use any RetryLane even if it's the one currently rendering
- // since we're leaving it behind on this node.
+ var schedulerPriorityLevel;
+
+ switch (lanesToEventPriority(nextLanes)) {
+ case DiscreteEventPriority:
+ schedulerPriorityLevel = ImmediatePriority;
+ break;
- workInProgress.lanes = SomeRetryLane;
- }
- }
+ case ContinuousEventPriority:
+ schedulerPriorityLevel = UserBlockingPriority;
+ break;
- if (renderState.isBackwards) {
- // The effect list of the backwards tail will have been added
- // to the end. This breaks the guarantee that life-cycles fire in
- // sibling order but that isn't a strong guarantee promised by React.
- // Especially since these might also just pop in during future commits.
- // Append to the beginning of the list.
- renderedTail.sibling = workInProgress.child;
- workInProgress.child = renderedTail;
- } else {
- var previousSibling = renderState.last;
+ case DefaultEventPriority:
+ schedulerPriorityLevel = NormalPriority$1;
+ break;
- if (previousSibling !== null) {
- previousSibling.sibling = renderedTail;
- } else {
- workInProgress.child = renderedTail;
- }
+ case IdleEventPriority:
+ schedulerPriorityLevel = IdlePriority;
+ break;
- renderState.last = renderedTail;
- }
- }
+ default:
+ schedulerPriorityLevel = NormalPriority$1;
+ break;
+ }
- if (renderState.tail !== null) {
- // We still have tail rows to render.
- // Pop a row.
- var next = renderState.tail;
- renderState.rendering = next;
- renderState.tail = next.sibling;
- renderState.renderingStartTime = now$1();
- next.sibling = null; // Restore the context.
- // TODO: We can probably just avoid popping it instead and only
- // setting it the first time we go from not suspended to suspended.
+ var newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
+ root.callbackPriority = newCallbackPriority;
+ root.callbackNode = newCallbackNode;
+ return newCallbackPriority;
+ }
+}
- var suspenseContext = suspenseStackCursor.current;
+function getContinuationForRoot(root, originalCallbackNode) {
+ // This is called at the end of `performConcurrentWorkOnRoot` to determine
+ // if we need to schedule a continuation task.
+ //
+ // Usually `scheduleTaskForRootDuringMicrotask` only runs inside a microtask;
+ // however, since most of the logic for determining if we need a continuation
+ // versus a new task is the same, we cheat a bit and call it here. This is
+ // only safe to do because we know we're at the end of the browser task.
+ // So although it's not an actual microtask, it might as well be.
+ scheduleTaskForRootDuringMicrotask(root, now$1());
- if (didSuspendAlready) {
- suspenseContext = setShallowSuspenseListContext(suspenseContext, ForceSuspenseFallback);
- } else {
- suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
- }
+ if (root.callbackNode === originalCallbackNode) {
+ // The task node scheduled for this root is the same one that's
+ // currently executed. Need to return a continuation.
+ return performConcurrentWorkOnRoot.bind(null, root);
+ }
- pushSuspenseListContext(workInProgress, suspenseContext); // Do a pass over the next row.
- // Don't bubble properties in this case.
+ return null;
+}
+var fakeActCallbackNode = {};
- return next;
- }
+function scheduleCallback(priorityLevel, callback) {
+ if (ReactSharedInternals.actQueue !== null) {
+ // Special case: We're inside an `act` scope (a testing utility).
+ // Instead of scheduling work in the host environment, add it to a
+ // fake internal queue that's managed by the `act` implementation.
+ ReactSharedInternals.actQueue.push(callback);
+ return fakeActCallbackNode;
+ } else {
+ return scheduleCallback$3(priorityLevel, callback);
+ }
+}
- bubbleProperties(workInProgress);
- return null;
- }
+function cancelCallback(callbackNode) {
+ if (callbackNode === fakeActCallbackNode) ; else if (callbackNode !== null) {
+ cancelCallback$1(callbackNode);
+ }
+}
- case ScopeComponent:
- {
+function scheduleImmediateTask(cb) {
+ if (ReactSharedInternals.actQueue !== null) {
+ // Special case: Inside an `act` scope, we push microtasks to the fake `act`
+ // callback queue. This is because we currently support calling `act`
+ // without awaiting the result. The plan is to deprecate that, and require
+ // that you always await the result so that the microtasks have a chance to
+ // run. But it hasn't happened yet.
+ ReactSharedInternals.actQueue.push(function () {
+ cb();
+ return null;
+ });
+ } // TODO: Can we land supportsMicrotasks? Which environments don't support it?
+ // Alternatively, can we move this check to the host config?
- break;
- }
- case OffscreenComponent:
- case LegacyHiddenComponent:
- {
- popSuspenseHandler(workInProgress);
- popHiddenContext(workInProgress);
- var _nextState = workInProgress.memoizedState;
- var nextIsHidden = _nextState !== null; // Schedule a Visibility effect if the visibility has changed
+ {
+ scheduleMicrotask(function () {
+ // In Safari, appending an iframe forces microtasks to run.
+ // https://github.com/facebook/react/issues/22459
+ // We don't support running callbacks in the middle of render
+ // or commit so we need to check against that.
+ var executionContext = getExecutionContext();
- {
- if (current !== null) {
- var _prevState = current.memoizedState;
- var prevIsHidden = _prevState !== null;
+ if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
+ // Note that this would still prematurely flush the callbacks
+ // if this happens outside render or commit phase (e.g. in an event).
+ // Intentionally using a macrotask instead of a microtask here. This is
+ // wrong semantically but it prevents an infinite loop. The bug is
+ // Safari's, not ours, so we just do our best to not crash even though
+ // the behavior isn't completely correct.
+ scheduleCallback$3(ImmediatePriority, cb);
+ return;
+ }
- if (prevIsHidden !== nextIsHidden) {
- workInProgress.flags |= Visibility;
- }
- } else {
- // On initial mount, we only need a Visibility effect if the tree
- // is hidden.
- if (nextIsHidden) {
- workInProgress.flags |= Visibility;
- }
- }
- }
+ cb();
+ });
+ }
+}
- if (!nextIsHidden || !disableLegacyMode ) {
- bubbleProperties(workInProgress);
- } else {
- // Don't bubble properties for hidden children unless we're rendering
- // at offscreen priority.
- if (includesSomeLane(renderLanes, OffscreenLane) && // Also don't bubble if the tree suspended
- (workInProgress.flags & DidCapture) === NoLanes) {
- bubbleProperties(workInProgress); // Check if there was an insertion or update in the hidden subtree.
- // If so, we need to hide those nodes in the commit phase, so
- // schedule a visibility effect.
+function requestTransitionLane( // This argument isn't used, it's only here to encourage the caller to
+// check that it's inside a transition before calling this function.
+// TODO: Make this non-nullable. Requires a tweak to useOptimistic.
+transition) {
+ // The algorithm for assigning an update to a lane should be stable for all
+ // updates at the same priority within the same event. To do this, the
+ // inputs to the algorithm must be the same.
+ //
+ // The trick we use is to cache the first of each of these inputs within an
+ // event. Then reset the cached values once we can be sure the event is
+ // over. Our heuristic for that is whenever we enter a concurrent work loop.
+ if (currentEventTransitionLane === NoLane) {
+ // All transitions within the same event are assigned the same lane.
+ currentEventTransitionLane = claimNextTransitionLane();
+ }
- if (workInProgress.subtreeFlags & (Placement | Update)) {
- workInProgress.flags |= Visibility;
- }
- }
- }
+ return currentEventTransitionLane;
+}
+function didCurrentEventScheduleTransition() {
+ return currentEventTransitionLane !== NoLane;
+}
- var offscreenQueue = workInProgress.updateQueue;
+function coerceFormActionProp(actionProp) {
+ // This should match the logic in ReactDOMComponent
+ if (actionProp == null || typeof actionProp === 'symbol' || typeof actionProp === 'boolean') {
+ return null;
+ } else if (typeof actionProp === 'function') {
+ return actionProp;
+ } else {
+ {
+ checkAttributeStringCoercion(actionProp, 'action');
+ }
- if (offscreenQueue !== null) {
- var _retryQueue3 = offscreenQueue.retryQueue;
- scheduleRetryEffect(workInProgress, _retryQueue3);
- }
+ return sanitizeURL('' + actionProp);
+ }
+}
- {
- var _previousCache2 = null;
+function createFormDataWithSubmitter(form, submitter) {
+ // The submitter's value should be included in the FormData.
+ // It should be in the document order in the form.
+ // Since the FormData constructor invokes the formdata event it also
+ // needs to be available before that happens so after construction it's too
+ // late. We use a temporary fake node for the duration of this event.
+ // TODO: FormData takes a second argument that it's the submitter but this
+ // is fairly new so not all browsers support it yet. Switch to that technique
+ // when available.
+ var temp = submitter.ownerDocument.createElement('input');
+ temp.name = submitter.name;
+ temp.value = submitter.value;
- if (current !== null && current.memoizedState !== null && current.memoizedState.cachePool !== null) {
- _previousCache2 = current.memoizedState.cachePool.pool;
- }
+ if (form.id) {
+ temp.setAttribute('form', form.id);
+ }
- var _cache2 = null;
+ submitter.parentNode.insertBefore(temp, submitter);
+ var formData = new FormData(form);
+ temp.parentNode.removeChild(temp);
+ return formData;
+}
+/**
+ * This plugin invokes action functions on forms, inputs and buttons if
+ * the form doesn't prevent default.
+ */
- if (workInProgress.memoizedState !== null && workInProgress.memoizedState.cachePool !== null) {
- _cache2 = workInProgress.memoizedState.cachePool.pool;
- }
- if (_cache2 !== _previousCache2) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive$1;
- }
- }
+function extractEvents$1(dispatchQueue, domEventName, maybeTargetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
+ if (domEventName !== 'submit') {
+ return;
+ }
- popTransition(workInProgress, current);
- return null;
- }
+ if (!maybeTargetInst || maybeTargetInst.stateNode !== nativeEventTarget) {
+ // If we're inside a parent root that itself is a parent of this root, then
+ // its deepest target won't be the actual form that's being submitted.
+ return;
+ }
- case CacheComponent:
- {
- {
- var _previousCache3 = null;
+ var formInst = maybeTargetInst;
+ var form = nativeEventTarget;
+ var action = coerceFormActionProp(getFiberCurrentPropsFromNode(form).action);
+ var submitter = nativeEvent.submitter;
+ var submitterAction;
- if (current !== null) {
- _previousCache3 = current.memoizedState.cache;
- }
+ if (submitter) {
+ var submitterProps = getFiberCurrentPropsFromNode(submitter);
+ submitterAction = submitterProps ? coerceFormActionProp(submitterProps.formAction) : // The built-in Flow type is ?string, wider than the spec
+ submitter.getAttribute('formAction');
- var _cache3 = workInProgress.memoizedState.cache;
+ if (submitterAction !== null) {
+ // The submitter overrides the form action.
+ action = submitterAction; // If the action is a function, we don't want to pass its name
+ // value to the FormData since it's controlled by the server.
- if (_cache3 !== _previousCache3) {
- // Run passive effects to retain/release the cache.
- workInProgress.flags |= Passive$1;
- }
+ submitter = null;
+ }
+ }
- popCacheProvider(workInProgress);
- bubbleProperties(workInProgress);
- }
+ var event = new SyntheticEvent('action', 'action', null, nativeEvent, nativeEventTarget);
- return null;
- }
+ function submitForm() {
+ if (nativeEvent.defaultPrevented) {
+ // An earlier event prevented form submission. If a transition update was
+ // also scheduled, we should trigger a pending form status — even if
+ // no action function was provided.
+ if (didCurrentEventScheduleTransition()) {
+ // We're going to set the pending form status, but because the submission
+ // was prevented, we should not fire the action function.
+ var formData = submitter ? createFormDataWithSubmitter(form, submitter) : new FormData(form);
+ var pendingState = {
+ pending: true,
+ data: formData,
+ method: form.method,
+ action: action
+ };
- case TracingMarkerComponent:
- {
+ {
+ Object.freeze(pendingState);
+ }
- return null;
+ startHostTransition(formInst, pendingState, // Pass `null` as the action
+ // TODO: Consider splitting up startHostTransition into two separate
+ // functions, one that sets the form status and one that invokes
+ // the action.
+ null, formData);
}
- }
+ } else if (typeof action === 'function') {
+ // A form action was provided. Prevent native navigation.
+ event.preventDefault(); // Dispatch the action and set a pending form status.
- throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
-}
+ var _formData = submitter ? createFormDataWithSubmitter(form, submitter) : new FormData(form);
-function unwindWork(current, workInProgress, renderLanes) {
- // Note: This intentionally doesn't check if we're hydrating because comparing
- // to the current tree provider fiber is just as fast and less error-prone.
- // Ideally we would have a special version of the work loop only
- // for hydration.
- popTreeContext(workInProgress);
+ var _pendingState = {
+ pending: true,
+ data: _formData,
+ method: form.method,
+ action: action
+ };
- switch (workInProgress.tag) {
- case ClassComponent:
{
+ Object.freeze(_pendingState);
+ }
- var flags = workInProgress.flags;
-
- if (flags & ShouldCapture) {
- workInProgress.flags = flags & ~ShouldCapture | DidCapture;
+ startHostTransition(formInst, _pendingState, action, _formData);
+ } else ;
+ }
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- transferActualDuration(workInProgress);
- }
+ dispatchQueue.push({
+ event: event,
+ listeners: [{
+ instance: null,
+ listener: submitForm,
+ currentTarget: form
+ }]
+ });
+}
+function dispatchReplayedFormAction(formInst, form, action, formData) {
+ var pendingState = {
+ pending: true,
+ data: formData,
+ method: form.method,
+ action: action
+ };
- return workInProgress;
- }
+ {
+ Object.freeze(pendingState);
+ }
- return null;
- }
+ startHostTransition(formInst, pendingState, action, formData);
+}
- case HostRoot:
- {
+registerSimpleEvents();
+registerEvents$1();
+registerEvents$2();
+registerEvents();
+registerEvents$3();
- {
- popCacheProvider(workInProgress);
- }
- popHostContainer(workInProgress);
- var _flags = workInProgress.flags;
+function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
+ // TODO: we should remove the concept of a "SimpleEventPlugin".
+ // This is the basic functionality of the event system. All
+ // the other plugins are essentially polyfills. So the plugin
+ // should probably be inlined somewhere and have its logic
+ // be core the to event system. This would potentially allow
+ // us to ship builds of React without the polyfilled plugins below.
+ extractEvents$2(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
+ var shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0; // We don't process these events unless we are in the
+ // event's native "bubble" phase, which means that we're
+ // not in the capture phase. That's because we emulate
+ // the capture phase here still. This is a trade-off,
+ // because in an ideal world we would not emulate and use
+ // the phases properly, like we do with the SimpleEvent
+ // plugin. However, the plugins below either expect
+ // emulation (EnterLeave) or use state localized to that
+ // plugin (BeforeInput, Change, Select). The state in
+ // these modules complicates things, as you'll essentially
+ // get the case where the capture phase event might change
+ // state, only for the following bubble event to come in
+ // later and not trigger anything as the state now
+ // invalidates the heuristics of the event plugin. We
+ // could alter all these plugins to work in such ways, but
+ // that might cause other unknown side-effects that we
+ // can't foresee right now.
- if ((_flags & ShouldCapture) !== NoFlags$1 && (_flags & DidCapture) === NoFlags$1) {
- // There was an error during render that wasn't captured by a suspense
- // boundary. Do a second pass on the root to unmount the children.
- workInProgress.flags = _flags & ~ShouldCapture | DidCapture;
- return workInProgress;
- } // We unwound to the root without completing it. Exit.
+ if (shouldProcessPolyfillPlugins) {
+ extractEvents$4(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ extractEvents$3(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ extractEvents$6(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ extractEvents$1(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget);
+ }
+} // List of events that need to be individually attached to media elements.
- return null;
- }
+var mediaEventTypes = ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'resize', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting']; // We should not delegate these events to the container, but rather
+// set them on the actual target element itself. This is primarily
+// because these events do not consistently bubble in the DOM.
- case HostHoistable:
- case HostSingleton:
- case HostComponent:
- {
- // TODO: popHydrationState
- popHostContext(workInProgress);
- return null;
- }
+var nonDelegatedEvents = new Set(['cancel', 'close', 'invalid', 'load', 'scroll', 'scrollend', 'toggle'].concat(mediaEventTypes));
- case SuspenseComponent:
- {
- popSuspenseHandler(workInProgress);
- var suspenseState = workInProgress.memoizedState;
+function executeDispatch(event, listener, currentTarget) {
+ event.currentTarget = currentTarget;
- if (suspenseState !== null && suspenseState.dehydrated !== null) {
- if (workInProgress.alternate === null) {
- throw new Error('Threw in newly mounted dehydrated component. This is likely a bug in ' + 'React. Please file an issue.');
- }
+ try {
+ listener(event);
+ } catch (error) {
+ reportGlobalError(error);
+ }
- resetHydrationState();
- }
+ event.currentTarget = null;
+}
- var _flags2 = workInProgress.flags;
+function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
+ var previousInstance;
- if (_flags2 & ShouldCapture) {
- workInProgress.flags = _flags2 & ~ShouldCapture | DidCapture; // Captured a suspense effect. Re-render the boundary.
+ if (inCapturePhase) {
+ for (var i = dispatchListeners.length - 1; i >= 0; i--) {
+ var _dispatchListeners$i = dispatchListeners[i],
+ instance = _dispatchListeners$i.instance,
+ currentTarget = _dispatchListeners$i.currentTarget,
+ listener = _dispatchListeners$i.listener;
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- transferActualDuration(workInProgress);
- }
+ if (instance !== previousInstance && event.isPropagationStopped()) {
+ return;
+ }
- return workInProgress;
- }
+ executeDispatch(event, listener, currentTarget);
+ previousInstance = instance;
+ }
+ } else {
+ for (var _i = 0; _i < dispatchListeners.length; _i++) {
+ var _dispatchListeners$_i = dispatchListeners[_i],
+ _instance = _dispatchListeners$_i.instance,
+ _currentTarget = _dispatchListeners$_i.currentTarget,
+ _listener = _dispatchListeners$_i.listener;
- return null;
+ if (_instance !== previousInstance && event.isPropagationStopped()) {
+ return;
}
- case SuspenseListComponent:
- {
- popSuspenseListContext(workInProgress); // SuspenseList doesn't actually catch anything. It should've been
- // caught by a nested boundary. If not, it should bubble through.
+ executeDispatch(event, _listener, _currentTarget);
+ previousInstance = _instance;
+ }
+ }
+}
- return null;
- }
+function processDispatchQueue(dispatchQueue, eventSystemFlags) {
+ var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
- case HostPortal:
- popHostContainer(workInProgress);
- return null;
+ for (var i = 0; i < dispatchQueue.length; i++) {
+ var _dispatchQueue$i = dispatchQueue[i],
+ event = _dispatchQueue$i.event,
+ listeners = _dispatchQueue$i.listeners;
+ processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
+ }
+}
- case ContextProvider:
- var context;
+function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
+ var nativeEventTarget = getEventTarget(nativeEvent);
+ var dispatchQueue = [];
+ extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
+ processDispatchQueue(dispatchQueue, eventSystemFlags);
+}
- {
- context = workInProgress.type;
- }
+function listenToNonDelegatedEvent(domEventName, targetElement) {
+ {
+ if (!nonDelegatedEvents.has(domEventName)) {
+ error('Did not expect a listenToNonDelegatedEvent() call for "%s". ' + 'This is a bug in React. Please file an issue.', domEventName);
+ }
+ }
- popProvider(context, workInProgress);
- return null;
+ var isCapturePhaseListener = false;
+ var listenerSet = getEventListenerSet(targetElement);
+ var listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener);
- case OffscreenComponent:
- case LegacyHiddenComponent:
- {
- popSuspenseHandler(workInProgress);
- popHiddenContext(workInProgress);
- popTransition(workInProgress, current);
- var _flags3 = workInProgress.flags;
+ if (!listenerSet.has(listenerSetKey)) {
+ addTrappedEventListener(targetElement, domEventName, IS_NON_DELEGATED, isCapturePhaseListener);
+ listenerSet.add(listenerSetKey);
+ }
+}
+function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
+ {
+ if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
+ error('Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' + 'This is a bug in React. Please file an issue.', domEventName);
+ }
+ }
- if (_flags3 & ShouldCapture) {
- workInProgress.flags = _flags3 & ~ShouldCapture | DidCapture; // Captured a suspense effect. Re-render the boundary.
+ var eventSystemFlags = 0;
- if ((workInProgress.mode & ProfileMode) !== NoMode) {
- transferActualDuration(workInProgress);
- }
+ if (isCapturePhaseListener) {
+ eventSystemFlags |= IS_CAPTURE_PHASE;
+ }
- return workInProgress;
+ addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
+} // This is only used by createEventHandle when the
+var listeningMarker = '_reactListening' + Math.random().toString(36).slice(2);
+function listenToAllSupportedEvents(rootContainerElement) {
+ if (!rootContainerElement[listeningMarker]) {
+ rootContainerElement[listeningMarker] = true;
+ allNativeEvents.forEach(function (domEventName) {
+ // We handle selectionchange separately because it
+ // doesn't bubble and needs to be on the document.
+ if (domEventName !== 'selectionchange') {
+ if (!nonDelegatedEvents.has(domEventName)) {
+ listenToNativeEvent(domEventName, false, rootContainerElement);
}
- return null;
+ listenToNativeEvent(domEventName, true, rootContainerElement);
}
+ });
+ var ownerDocument = rootContainerElement.nodeType === DOCUMENT_NODE ? rootContainerElement : rootContainerElement.ownerDocument;
- case CacheComponent:
- {
- popCacheProvider(workInProgress);
+ if (ownerDocument !== null) {
+ // The selectionchange event also needs deduplication
+ // but it is attached to the document.
+ if (!ownerDocument[listeningMarker]) {
+ ownerDocument[listeningMarker] = true;
+ listenToNativeEvent('selectionchange', false, ownerDocument);
}
+ }
+ }
+}
- return null;
+function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
+ var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the event will be
+ // active and not passive.
- case TracingMarkerComponent:
+ var isPassiveListener = undefined;
- return null;
+ if (passiveBrowserEventsSupported) {
+ // Browsers introduced an intervention, making these events
+ // passive by default on document. React doesn't bind them
+ // to document anymore, but changing this now would undo
+ // the performance wins from the change. So we emulate
+ // the existing behavior manually on the roots now.
+ // https://github.com/facebook/react/issues/19651
+ if (domEventName === 'touchstart' || domEventName === 'touchmove' || domEventName === 'wheel') {
+ isPassiveListener = true;
+ }
+ }
- default:
- return null;
+ targetContainer = targetContainer;
+
+
+ if (isCapturePhaseListener) {
+ if (isPassiveListener !== undefined) {
+ addEventCaptureListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
+ } else {
+ addEventCaptureListener(targetContainer, domEventName, listener);
+ }
+ } else {
+ if (isPassiveListener !== undefined) {
+ addEventBubbleListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
+ } else {
+ addEventBubbleListener(targetContainer, domEventName, listener);
+ }
}
}
-function unwindInterruptedWork(current, interruptedWork, renderLanes) {
- // Note: This intentionally doesn't check if we're hydrating because comparing
- // to the current tree provider fiber is just as fast and less error-prone.
- // Ideally we would have a special version of the work loop only
- // for hydration.
- popTreeContext(interruptedWork);
+function isMatchingRootContainer(grandContainer, targetContainer) {
+ return grandContainer === targetContainer || grandContainer.nodeType === COMMENT_NODE && grandContainer.parentNode === targetContainer;
+}
- switch (interruptedWork.tag) {
- case ClassComponent:
- {
+function dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
+ var ancestorInst = targetInst;
- break;
- }
+ if ((eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && (eventSystemFlags & IS_NON_DELEGATED) === 0) {
+ var targetContainerNode = targetContainer; // If we are using the legacy FB support flag, we
- case HostRoot:
- {
+ if (targetInst !== null) {
+ // The below logic attempts to work out if we need to change
+ // the target fiber to a different ancestor. We had similar logic
+ // in the legacy event system, except the big difference between
+ // systems is that the modern event system now has an event listener
+ // attached to each React Root and React Portal Root. Together,
+ // the DOM nodes representing these roots are the "rootContainer".
+ // To figure out which ancestor instance we should use, we traverse
+ // up the fiber tree from the target instance and attempt to find
+ // root boundaries that match that of our current "rootContainer".
+ // If we find that "rootContainer", we find the parent fiber
+ // sub-tree for that root and make that our ancestor instance.
+ var node = targetInst;
- {
- popCacheProvider(interruptedWork);
+ mainLoop: while (true) {
+ if (node === null) {
+ return;
}
- popHostContainer(interruptedWork);
- break;
- }
-
- case HostHoistable:
- case HostSingleton:
- case HostComponent:
- {
- popHostContext(interruptedWork);
- break;
- }
-
- case HostPortal:
- popHostContainer(interruptedWork);
- break;
- case SuspenseComponent:
- popSuspenseHandler(interruptedWork);
- break;
+ var nodeTag = node.tag;
- case SuspenseListComponent:
- popSuspenseListContext(interruptedWork);
- break;
+ if (nodeTag === HostRoot || nodeTag === HostPortal) {
+ var container = node.stateNode.containerInfo;
- case ContextProvider:
- var context;
+ if (isMatchingRootContainer(container, targetContainerNode)) {
+ break;
+ }
- {
- context = interruptedWork.type;
- }
+ if (nodeTag === HostPortal) {
+ // The target is a portal, but it's not the rootContainer we're looking for.
+ // Normally portals handle their own events all the way down to the root.
+ // So we should be able to stop now. However, we don't know if this portal
+ // was part of *our* root.
+ var grandNode = node.return;
- popProvider(context, interruptedWork);
- break;
+ while (grandNode !== null) {
+ var grandTag = grandNode.tag;
- case OffscreenComponent:
- case LegacyHiddenComponent:
- popSuspenseHandler(interruptedWork);
- popHiddenContext(interruptedWork);
- popTransition(interruptedWork, current);
- break;
+ if (grandTag === HostRoot || grandTag === HostPortal) {
+ var grandContainer = grandNode.stateNode.containerInfo;
- case CacheComponent:
- {
- popCacheProvider(interruptedWork);
- }
+ if (isMatchingRootContainer(grandContainer, targetContainerNode)) {
+ // This is the rootContainer we're looking for and we found it as
+ // a parent of the Portal. That means we can ignore it because the
+ // Portal will bubble through to us.
+ return;
+ }
+ }
- break;
- }
-}
+ grandNode = grandNode.return;
+ }
+ } // Now we need to find it's corresponding host fiber in the other
+ // tree. To do this we can use getClosestInstanceFromNode, but we
+ // need to validate that the fiber is a host instance, otherwise
+ // we need to traverse up through the DOM till we find the correct
+ // node that is from the other tree.
-function getCacheForType(resourceType) {
- var cache = readContext(CacheContext);
- var cacheForType = cache.data.get(resourceType);
+ while (container !== null) {
+ var parentNode = getClosestInstanceFromNode(container);
- if (cacheForType === undefined) {
- cacheForType = resourceType();
- cache.data.set(resourceType, cacheForType);
- }
+ if (parentNode === null) {
+ return;
+ }
- return cacheForType;
-}
+ var parentTag = parentNode.tag;
-var DefaultAsyncDispatcher = {
- getCacheForType: getCacheForType
-};
+ if (parentTag === HostComponent || parentTag === HostText || parentTag === HostHoistable || parentTag === HostSingleton) {
+ node = ancestorInst = parentNode;
+ continue mainLoop;
+ }
-{
- DefaultAsyncDispatcher.getOwner = function () {
- return currentOwner;
- };
-}
+ container = container.parentNode;
+ }
+ }
-if (typeof Symbol === 'function' && Symbol.for) {
- var symbolFor = Symbol.for;
- symbolFor('selector.component');
- symbolFor('selector.has_pseudo_class');
- symbolFor('selector.role');
- symbolFor('selector.test_id');
- symbolFor('selector.text');
-}
-var commitHooks = [];
-function onCommitRoot() {
- {
- commitHooks.forEach(function (commitHook) {
- return commitHook();
- });
+ node = node.return;
+ }
+ }
}
-}
-
-function isConcurrentActEnvironment() {
- {
- var isReactActEnvironmentGlobal = // $FlowFixMe[cannot-resolve-name] Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
- typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined' ? // $FlowFixMe[cannot-resolve-name]
- IS_REACT_ACT_ENVIRONMENT : undefined;
- if (!isReactActEnvironmentGlobal && ReactSharedInternals.actQueue !== null) {
- // TODO: Include link to relevant documentation page.
- error('The current testing environment is not configured to support ' + 'act(...)');
- }
+ batchedUpdates$1(function () {
+ return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst);
+ });
+}
- return isReactActEnvironmentGlobal;
- }
+function createDispatchListener(instance, listener, currentTarget) {
+ return {
+ instance: instance,
+ listener: listener,
+ currentTarget: currentTarget
+ };
}
-var PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
-var NoContext =
-/* */
-0;
-var RenderContext =
-/* */
-2;
-var CommitContext =
-/* */
-4;
-var RootInProgress = 0;
-var RootFatalErrored = 1;
-var RootErrored = 2;
-var RootSuspended = 3;
-var RootSuspendedWithDelay = 4;
-var RootCompleted = 5;
-var RootDidNotComplete = 6; // Describes where we are in the React execution stack
+function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly, nativeEvent) {
+ var captureName = reactName !== null ? reactName + 'Capture' : null;
+ var reactEventName = inCapturePhase ? captureName : reactName;
+ var listeners = [];
+ var instance = targetFiber;
+ var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path.
-var executionContext = NoContext; // The root we're working on
+ while (instance !== null) {
+ var _instance2 = instance,
+ stateNode = _instance2.stateNode,
+ tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e.