/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols'; import type {Wakeable} from 'shared/ReactTypes'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; import type {Lanes, Lane} from './ReactFiberLane.new'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; import type {EventPriority} from './ReactEventPriorities.new'; import type { PendingTransitionCallbacks, PendingBoundaries, Transition, TransitionAbort, } from './ReactFiberTracingMarkerComponent.new'; import type {OffscreenInstance} from './ReactFiberOffscreenComponent'; import { warnAboutDeprecatedLifecycles, replayFailedUnitOfWorkWithInvokeGuardedCallback, enableCreateEventHandleAPI, enableProfilerTimer, enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, enableProfilerNestedUpdateScheduledHook, deferRenderPhaseUpdateToNextBatch, enableDebugTracing, enableSchedulingProfiler, disableSchedulerTimeoutInWorkLoop, skipUnmountedBoundaries, enableUpdaterTracking, enableCache, enableTransitionTracing, useModernStrictMode, } from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import is from 'shared/objectIs'; import { // Aliased because `act` will override and push to an internal queue scheduleCallback as Scheduler_scheduleCallback, cancelCallback as Scheduler_cancelCallback, shouldYield, requestPaint, now, ImmediatePriority as ImmediateSchedulerPriority, UserBlockingPriority as UserBlockingSchedulerPriority, NormalPriority as NormalSchedulerPriority, IdlePriority as IdleSchedulerPriority, } from './Scheduler'; import { flushSyncCallbacks, flushSyncCallbacksOnlyInLegacyMode, scheduleSyncCallback, scheduleLegacySyncCallback, } from './ReactFiberSyncTaskQueue.new'; import { logCommitStarted, logCommitStopped, logLayoutEffectsStarted, logLayoutEffectsStopped, logPassiveEffectsStarted, logPassiveEffectsStopped, logRenderStarted, logRenderStopped, } from './DebugTracing'; import { resetAfterCommit, scheduleTimeout, cancelTimeout, noTimeout, afterActiveInstanceBlur, getCurrentEventPriority, supportsMicrotasks, errorHydratingContainer, scheduleMicrotask, prepareRendererToRender, resetRendererAfterRender, } from './ReactFiberHostConfig'; import { createWorkInProgress, assignFiberPropertiesInDEV, resetWorkInProgress, } from './ReactFiber.new'; import {isRootDehydrated} from './ReactFiberShellHydration'; import {didSuspendOrErrorWhileHydratingDEV} from './ReactFiberHydrationContext.new'; import { NoMode, ProfileMode, ConcurrentMode, StrictLegacyMode, StrictEffectsMode, } from './ReactTypeOfMode'; import { HostRoot, IndeterminateComponent, ClassComponent, SuspenseComponent, SuspenseListComponent, OffscreenComponent, FunctionComponent, ForwardRef, MemoComponent, SimpleMemoComponent, Profiler, } from './ReactWorkTags'; import {ConcurrentRoot, LegacyRoot} from './ReactRootTags'; import type {Flags} from './ReactFiberFlags'; import { NoFlags, Incomplete, StoreConsistency, HostEffectMask, ForceClientRender, BeforeMutationMask, MutationMask, LayoutMask, PassiveMask, PlacementDEV, Visibility, MountPassiveDev, MountLayoutDev, } from './ReactFiberFlags'; import { NoLanes, NoLane, SyncLane, NoTimestamp, claimNextTransitionLane, claimNextRetryLane, includesSomeLane, isSubsetOfLanes, mergeLanes, removeLanes, pickArbitraryLane, includesNonIdleWork, includesOnlyRetries, includesOnlyTransitions, includesBlockingLane, includesExpiredLane, getNextLanes, markStarvedLanesAsExpired, getLanesToRetrySynchronouslyOnError, getMostRecentEventTime, markRootUpdated, markRootSuspended as markRootSuspended_dontCallThisOneDirectly, markRootPinged, markRootEntangled, markRootFinished, getHighestPriorityLane, addFiberToLanesMap, movePendingFibersToMemoized, addTransitionToLanesMap, getTransitionsForLanes, } from './ReactFiberLane.new'; import { DiscreteEventPriority, ContinuousEventPriority, DefaultEventPriority, IdleEventPriority, getCurrentUpdatePriority, setCurrentUpdatePriority, lowerEventPriority, lanesToEventPriority, } from './ReactEventPriorities.new'; import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new'; import {completeWork} from './ReactFiberCompleteWork.new'; import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork.new'; import { throwException, createRootErrorUpdate, createClassErrorUpdate, } from './ReactFiberThrow.new'; import { commitBeforeMutationEffects, commitLayoutEffects, commitMutationEffects, commitPassiveEffectDurations, commitPassiveMountEffects, commitPassiveUnmountEffects, disappearLayoutEffects, reconnectPassiveEffects, reappearLayoutEffects, disconnectPassiveEffect, reportUncaughtErrorInDEV, invokeLayoutEffectMountInDEV, invokePassiveEffectMountInDEV, invokeLayoutEffectUnmountInDEV, invokePassiveEffectUnmountInDEV, } from './ReactFiberCommitWork.new'; import {enqueueUpdate} from './ReactFiberClassUpdateQueue.new'; import {resetContextDependencies} from './ReactFiberNewContext.new'; import { resetHooksAfterThrow, ContextOnlyDispatcher, getIsUpdatingOpaqueValueInRenderPhaseInDEV, } from './ReactFiberHooks.new'; import {DefaultCacheDispatcher} from './ReactFiberCache.new'; import { createCapturedValueAtFiber, type CapturedValue, } from './ReactCapturedValue'; import { enqueueConcurrentRenderForLane, finishQueueingConcurrentUpdates, getConcurrentlyUpdatedLanes, } from './ReactFiberConcurrentUpdates.new'; import { markNestedUpdateScheduled, recordCommitTime, resetNestedUpdateFlag, startProfilerTimer, stopProfilerTimerIfRunningAndRecordDelta, syncNestedUpdateFlag, } from './ReactProfilerTimer.new'; // DEV stuff import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; import ReactStrictModeWarnings from './ReactStrictModeWarnings.new'; import { isRendering as ReactCurrentDebugFiberIsRenderingInDEV, current as ReactCurrentFiberCurrent, resetCurrentFiber as resetCurrentDebugFiberInDEV, setCurrentFiber as setCurrentDebugFiberInDEV, } from './ReactCurrentFiber'; import { invokeGuardedCallback, hasCaughtError, clearCaughtError, } from 'shared/ReactErrorUtils'; import { isDevToolsPresent, markCommitStarted, markCommitStopped, markComponentRenderStopped, markComponentSuspended, markComponentErrored, markLayoutEffectsStarted, markLayoutEffectsStopped, markPassiveEffectsStarted, markPassiveEffectsStopped, markRenderStarted, markRenderYielded, markRenderStopped, onCommitRoot as onCommitRootDevTools, onPostCommitRoot as onPostCommitRootDevTools, } from './ReactFiberDevToolsHook.new'; import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors'; import {releaseCache} from './ReactFiberCacheComponent.new'; import { isLegacyActEnvironment, isConcurrentActEnvironment, } from './ReactFiberAct.new'; import {processTransitionCallbacks} from './ReactFiberTracingMarkerComponent.new'; import { resetWakeableStateAfterEachAttempt, resetThenableStateOnCompletion, suspendedThenableDidResolve, isTrackingSuspendedThenable, } from './ReactFiberThenable.new'; import {schedulePostPaintCallback} from './ReactPostPaintCallback'; const ceil = Math.ceil; const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; const { ReactCurrentDispatcher, ReactCurrentCache, ReactCurrentOwner, ReactCurrentBatchConfig, ReactCurrentActQueue, } = ReactSharedInternals; type ExecutionContext = number; export const NoContext = /* */ 0b000; const BatchedContext = /* */ 0b001; export const RenderContext = /* */ 0b010; export const CommitContext = /* */ 0b100; type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6; const RootInProgress = 0; const RootFatalErrored = 1; const RootErrored = 2; const RootSuspended = 3; const RootSuspendedWithDelay = 4; const RootCompleted = 5; const RootDidNotComplete = 6; // Describes where we are in the React execution stack let executionContext: ExecutionContext = NoContext; // The root we're working on let workInProgressRoot: FiberRoot | null = null; // The fiber we're working on let workInProgress: Fiber | null = null; // The lanes we're rendering let workInProgressRootRenderLanes: Lanes = NoLanes; // 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. let workInProgressIsSuspended: boolean = false; let workInProgressThrownValue: mixed = 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). let workInProgressRootDidAttachPingListener: boolean = 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 renderLanes. export let renderLanes: Lanes = NoLanes; // Whether to root completed, errored, suspended, etc. let workInProgressRootExitStatus: RootExitStatus = RootInProgress; // A fatal error, if one is thrown let workInProgressRootFatalError: mixed = null; // The work left over by components that were visited during this render. Only // includes unprocessed updates, not work in bailed out children. let workInProgressRootSkippedLanes: Lanes = NoLanes; // Lanes that were updated (in an interleaved event) during this render. let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes; // Lanes that were updated during the render phase (*not* an interleaved event). let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes; // Lanes that were pinged (in an interleaved event) during this render. let workInProgressRootPingedLanes: Lanes = NoLanes; // Errors that are thrown during the render phase. let workInProgressRootConcurrentErrors: Array< CapturedValue, > | null = null; // These are errors that we recovered from without surfacing them to the UI. // We will log them once the tree commits. let workInProgressRootRecoverableErrors: Array< CapturedValue, > | null = null; // The most recent time we committed a fallback. This lets us ensure a train // model where we don't commit new loading states in too quick succession. let globalMostRecentFallbackTime: number = 0; const FALLBACK_THROTTLE_MS: number = 500; // The absolute time for when we should start giving up on rendering // more and prefer CPU suspense heuristics instead. let workInProgressRootRenderTargetTime: number = Infinity; // How long a render is supposed to take before we start following CPU // suspense heuristics and opt out of rendering more content. const RENDER_TIMEOUT_MS = 500; let workInProgressTransitions: Array | null = null; export function getWorkInProgressTransitions(): null | Array { return workInProgressTransitions; } let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null; let currentEndTime: number | null = null; export function addTransitionStartCallbackToPendingTransition( transition: Transition, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = { transitionStart: [], transitionProgress: null, transitionComplete: null, markerProgress: null, markerIncomplete: null, markerComplete: null, }; } if (currentPendingTransitionCallbacks.transitionStart === null) { currentPendingTransitionCallbacks.transitionStart = []; } currentPendingTransitionCallbacks.transitionStart.push(transition); } } export function addMarkerProgressCallbackToPendingTransition( markerName: string, transitions: Set, pendingBoundaries: PendingBoundaries, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = ({ transitionStart: null, transitionProgress: null, transitionComplete: null, markerProgress: new Map(), markerIncomplete: null, markerComplete: null, }: PendingTransitionCallbacks); } if (currentPendingTransitionCallbacks.markerProgress === null) { currentPendingTransitionCallbacks.markerProgress = new Map(); } currentPendingTransitionCallbacks.markerProgress.set(markerName, { pendingBoundaries, transitions, }); } } export function addMarkerIncompleteCallbackToPendingTransition( markerName: string, transitions: Set, aborts: Array, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = { transitionStart: null, transitionProgress: null, transitionComplete: null, markerProgress: null, markerIncomplete: new Map(), markerComplete: null, }; } if (currentPendingTransitionCallbacks.markerIncomplete === null) { currentPendingTransitionCallbacks.markerIncomplete = new Map(); } currentPendingTransitionCallbacks.markerIncomplete.set(markerName, { transitions, aborts, }); } } export function addMarkerCompleteCallbackToPendingTransition( markerName: string, transitions: Set, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = { transitionStart: null, transitionProgress: null, transitionComplete: null, markerProgress: null, markerIncomplete: null, markerComplete: new Map(), }; } if (currentPendingTransitionCallbacks.markerComplete === null) { currentPendingTransitionCallbacks.markerComplete = new Map(); } currentPendingTransitionCallbacks.markerComplete.set( markerName, transitions, ); } } export function addTransitionProgressCallbackToPendingTransition( transition: Transition, boundaries: PendingBoundaries, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = { transitionStart: null, transitionProgress: new Map(), transitionComplete: null, markerProgress: null, markerIncomplete: null, markerComplete: null, }; } if (currentPendingTransitionCallbacks.transitionProgress === null) { currentPendingTransitionCallbacks.transitionProgress = new Map(); } currentPendingTransitionCallbacks.transitionProgress.set( transition, boundaries, ); } } export function addTransitionCompleteCallbackToPendingTransition( transition: Transition, ) { if (enableTransitionTracing) { if (currentPendingTransitionCallbacks === null) { currentPendingTransitionCallbacks = { transitionStart: null, transitionProgress: null, transitionComplete: [], markerProgress: null, markerIncomplete: null, markerComplete: null, }; } if (currentPendingTransitionCallbacks.transitionComplete === null) { currentPendingTransitionCallbacks.transitionComplete = []; } currentPendingTransitionCallbacks.transitionComplete.push(transition); } } function resetRenderTimer() { workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS; } export function getRenderTargetTime(): number { return workInProgressRootRenderTargetTime; } let hasUncaughtError = false; let firstUncaughtError = null; let legacyErrorBoundariesThatAlreadyFailed: Set | null = null; // Only used when enableProfilerNestedUpdateScheduledHook is true; // to track which root is currently committing layout effects. let rootCommittingMutationOrLayoutEffects: FiberRoot | null = null; let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsLanes: Lanes = NoLanes; let pendingPassiveProfilerEffects: Array = []; let pendingPassiveEffectsRemainingLanes: Lanes = NoLanes; let pendingPassiveTransitions: Array | null = null; // Use these to prevent an infinite loop of nested updates const NESTED_UPDATE_LIMIT = 50; let nestedUpdateCount: number = 0; let rootWithNestedUpdates: FiberRoot | null = null; let isFlushingPassiveEffects = false; let didScheduleUpdateDuringPassiveEffects = false; const NESTED_PASSIVE_UPDATE_LIMIT = 50; let nestedPassiveUpdateCount: number = 0; let rootWithPassiveNestedUpdates: FiberRoot | null = null; // If two updates are scheduled within the same event, we should treat their // event times as simultaneous, even if the actual clock time has advanced // between the first and second call. let currentEventTime: number = NoTimestamp; let currentEventTransitionLane: Lanes = NoLanes; let isRunningInsertionEffect = false; export function getWorkInProgressRoot(): FiberRoot | null { return workInProgressRoot; } export function getWorkInProgressRootRenderLanes(): Lanes { return workInProgressRootRenderLanes; } export function requestEventTime(): number { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // We're inside React, so it's fine to read the actual time. return now(); } // We're not inside React, so we may be in the middle of a browser event. if (currentEventTime !== NoTimestamp) { // Use the same start time for all updates until we enter React again. return currentEventTime; } // This is the first update since React yielded. Compute a new start time. currentEventTime = now(); return currentEventTime; } export function getCurrentTime(): number { return now(); } export function requestUpdateLane(fiber: Fiber): Lane { // Special cases const mode = fiber.mode; if ((mode & ConcurrentMode) === NoMode) { return (SyncLane: Lane); } else if ( !deferRenderPhaseUpdateToNextBatch && (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); } const isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { if (__DEV__ && ReactCurrentBatchConfig.transition !== null) { const transition = ReactCurrentBatchConfig.transition; if (!transition._updatedFibers) { transition._updatedFibers = new Set(); } transition._updatedFibers.add(fiber); } // 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(); } return currentEventTransitionLane; } // Updates originating inside certain React methods, like flushSync, have // their priority set by tracking it with a context variable. // // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. const updateLane: Lane = (getCurrentUpdatePriority(): any); if (updateLane !== NoLane) { return updateLane; } // This update originated outside React. Ask the host environment for an // appropriate priority, based on the type of event. // // The opaque type returned by the host config is internally a lane, so we can // use that directly. // TODO: Move this type conversion to the event priority module. const eventLane: Lane = (getCurrentEventPriority(): any); return eventLane; } function requestRetryLane(fiber: Fiber) { // This is a fork of `requestUpdateLane` designed specifically for Suspense // "retries" — a special update that attempts to flip a Suspense boundary // from its placeholder state to its primary/resolved state. // Special cases const mode = fiber.mode; if ((mode & ConcurrentMode) === NoMode) { return (SyncLane: Lane); } return claimNextRetryLane(); } export function scheduleUpdateOnFiber( root: FiberRoot, fiber: Fiber, lane: Lane, eventTime: number, ) { if (__DEV__) { if (isRunningInsertionEffect) { console.error('useInsertionEffect must not schedule updates.'); } } if (__DEV__) { if (isFlushingPassiveEffects) { didScheduleUpdateDuringPassiveEffects = true; } } // Mark that the root has a pending update. markRootUpdated(root, lane, eventTime); 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 workInProgressRootRenderPhaseUpdatedLanes = mergeLanes( workInProgressRootRenderPhaseUpdatedLanes, lane, ); } else { // This is a normal update, scheduled from outside the render phase. For // example, during an input event. if (enableUpdaterTracking) { if (isDevToolsPresent) { addFiberToLanesMap(root, fiber, lane); } } warnIfUpdatesNotWrappedWithActDEV(fiber); if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { if ( (executionContext & CommitContext) !== NoContext && root === rootCommittingMutationOrLayoutEffects ) { if (fiber.mode & ProfileMode) { let current: null | Fiber = fiber; while (current !== null) { if (current.tag === Profiler) { const {id, onNestedUpdateScheduled} = current.memoizedProps; if (typeof onNestedUpdateScheduled === 'function') { onNestedUpdateScheduled(id); } } current = current.return; } } } } if (enableTransitionTracing) { const transition = ReactCurrentBatchConfig.transition; if (transition !== null && transition.name != null) { if (transition.startTime === -1) { transition.startTime = now(); } addTransitionToLanesMap(root, transition, lane); } } 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. Unless the // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render // phase update. In that case, we don't treat render phase updates as if // they were interleaved, for backwards compat reasons. if ( deferRenderPhaseUpdateToNextBatch || (executionContext & RenderContext) === NoContext ) { workInProgressRootInterleavedUpdatedLanes = mergeLanes( workInProgressRootInterleavedUpdatedLanes, lane, ); } 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); } } ensureRootIsScheduled(root, eventTime); if ( lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode. !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy) ) { // 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(); flushSyncCallbacksOnlyInLegacyMode(); } } } export function scheduleInitialHydrationOnRoot( root: FiberRoot, lane: Lane, eventTime: number, ) { // 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. const current = root.current; current.lanes = lane; markRootUpdated(root, lane, eventTime); ensureRootIsScheduled(root, eventTime); } export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber): boolean { // Check if this is a render phase update. Only called by class components, // which special (deprecated) behavior for UNSAFE_componentWillReceive props. return ( // TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We // decided not to enable it. (!deferRenderPhaseUpdateToNextBatch || (fiber.mode & ConcurrentMode) === NoMode) && (executionContext & RenderContext) !== NoContext ); } // Use this function to schedule a task for a root. There's only one task per // root; if a task was already scheduled, we'll check to make sure the priority // of the existing task is the same as the priority of the next level that the // root has work on. This function is called on every update, and right before // exiting a task. function ensureRootIsScheduled(root: FiberRoot, currentTime: number) { const existingCallbackNode = root.callbackNode; // 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. const nextLanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, ); if (nextLanes === NoLanes) { // Special case: There's nothing to work on. if (existingCallbackNode !== null) { cancelCallback(existingCallbackNode); } root.callbackNode = null; root.callbackPriority = NoLane; return; } // We use the highest priority lane to represent the priority of the callback. const newCallbackPriority = getHighestPriorityLane(nextLanes); // Check if there's an existing task. We may be able to reuse it. const existingCallbackPriority = root.callbackPriority; if ( existingCallbackPriority === newCallbackPriority && // Special case related to `act`. If the currently scheduled task is a // Scheduler task, rather than an `act` task, cancel it and re-scheduled // on the `act` queue. !( __DEV__ && ReactCurrentActQueue.current !== null && existingCallbackNode !== fakeActCallbackNode ) ) { if (__DEV__) { // If we're going to re-use an existing task, it needs to exist. // Assume that discrete update microtasks are non-cancellable and null. // TODO: Temporary until we confirm this warning is not fired. if ( existingCallbackNode == null && existingCallbackPriority !== SyncLane ) { console.error( 'Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.', ); } } // The priority hasn't changed. We can reuse the existing task. Exit. return; } if (existingCallbackNode != null) { // Cancel the existing callback. We'll schedule a new one below. cancelCallback(existingCallbackNode); } // Schedule a new callback. let newCallbackNode; if (newCallbackPriority === SyncLane) { // Special case: Sync React callbacks are scheduled on a special // internal queue if (root.tag === LegacyRoot) { if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy !== null) { ReactCurrentActQueue.didScheduleLegacyUpdate = true; } scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root)); } else { scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } if (supportsMicrotasks) { // Flush the queue in a microtask. if (__DEV__ && ReactCurrentActQueue.current !== null) { // Inside `act`, use our internal `act` queue so that these get flushed // at the end of the current scope even when using the sync version // of `act`. ReactCurrentActQueue.current.push(flushSyncCallbacks); } else { scheduleMicrotask(() => { // 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. 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). flushSyncCallbacks(); } }); } } else { // Flush the queue in an Immediate task. scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks); } newCallbackNode = null; } else { let schedulerPriorityLevel; switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingSchedulerPriority; break; case DefaultEventPriority: schedulerPriorityLevel = NormalSchedulerPriority; break; case IdleEventPriority: schedulerPriorityLevel = IdleSchedulerPriority; break; default: schedulerPriorityLevel = NormalSchedulerPriority; break; } newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), ); } root.callbackPriority = newCallbackPriority; root.callbackNode = newCallbackNode; } // This is the entry point for every concurrent task, i.e. anything that // goes through Scheduler. function performConcurrentWorkOnRoot(root, didTimeout) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { resetNestedUpdateFlag(); } // Since we know we're in a React event, we can clear the current // event time. The next update will compute a new event time. currentEventTime = NoTimestamp; currentEventTransitionLane = NoLanes; 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. const originalCallbackNode = root.callbackNode; const didFlushPassiveEffects = flushPassiveEffects(); 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; } else { // Current task was not canceled. Continue. } } // Determine the next lanes to work on, using the fields stored // on the root. let lanes = getNextLanes( root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, ); 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. const shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && (disableSchedulerTimeoutInWorkLoop || !didTimeout); let exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes); if (exitStatus !== RootInProgress) { 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. const originallyAttemptedLanes = lanes; const errorRetryLanes = getLanesToRetrySynchronouslyOnError( root, originallyAttemptedLanes, ); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; exitStatus = recoverFromConcurrentError( root, originallyAttemptedLanes, errorRetryLanes, ); } } if (exitStatus === RootFatalErrored) { const fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); markRootSuspended(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; } 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); } 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. const renderWasConcurrent = !includesBlockingLane(root, lanes); const finishedWork: Fiber = (root.current.alternate: any); if ( renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork) ) { // A store was mutated in an interleaved event. Render again, // synchronously, to block further mutations. exitStatus = renderRootSync(root, lanes); // We need to check again if something threw if (exitStatus === RootErrored) { const originallyAttemptedLanes = lanes; const errorRetryLanes = getLanesToRetrySynchronouslyOnError( root, originallyAttemptedLanes, ); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; exitStatus = recoverFromConcurrentError( root, originallyAttemptedLanes, errorRetryLanes, ); // We assume the tree is now consistent because we didn't yield to any // concurrent events. } } if (exitStatus === RootFatalErrored) { const fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); markRootSuspended(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; } // FIXME: Need to check for RootDidNotComplete again. The factoring here // isn't ideal. } // 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. root.finishedWork = finishedWork; root.finishedLanes = lanes; finishConcurrentRender(root, exitStatus, lanes); } } ensureRootIsScheduled(root, now()); 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); } return null; } 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. const errorsFromFirstAttempt = workInProgressRootConcurrentErrors; const wasRootDehydrated = isRootDehydrated(root); 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. const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes); rootWorkInProgress.flags |= ForceClientRender; if (__DEV__) { errorHydratingContainer(root.containerInfo); } } const exitStatus = renderRootSync(root, errorRetryLanes); 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. 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. const 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. if (errorsFromSecondAttempt !== null) { queueRecoverableErrors(errorsFromSecondAttempt); } } else { // The UI failed to recover. } return exitStatus; } export function queueRecoverableErrors(errors: Array>) { if (workInProgressRootRecoverableErrors === null) { workInProgressRootRecoverableErrors = errors; } else { // $FlowFixMe[method-unbinding] workInProgressRootRecoverableErrors.push.apply( workInProgressRootRecoverableErrors, errors, ); } } function finishConcurrentRender(root, exitStatus, lanes) { switch (exitStatus) { case RootInProgress: case RootFatalErrored: { throw new Error('Root did not complete. This is a bug in React.'); } // Flow knows about invariant, so it complains if I add a break // statement, but eslint doesn't know about invariant, so it complains // if I do. eslint-disable-next-line no-fallthrough case RootErrored: { // We should have already attempted to retry this tree. If we reached // this point, it errored again. Commit it. commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, ); break; } case RootSuspended: { markRootSuspended(root, lanes); // We have an acceptable loading state. We need to figure out if we // should immediately commit it or wait a bit. if ( includesOnlyRetries(lanes) && // do not delay if we're inside an act() scope !shouldForceFlushFallbacksInDEV() ) { // This render only included retries, no updates. Throttle committing // retries so that we don't show too many loading states too quickly. const msUntilTimeout = globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now(); // Don't bother with a very short suspense time. if (msUntilTimeout > 10) { const nextLanes = getNextLanes(root, NoLanes); if (nextLanes !== NoLanes) { // There's additional work on this root. break; } const suspendedLanes = root.suspendedLanes; if (!isSubsetOfLanes(suspendedLanes, lanes)) { // We should prefer to render the fallback of at the last // suspended level. Ping the last suspended level to try // rendering it again. // FIXME: What if the suspended lanes are Idle? Should not restart. const eventTime = requestEventTime(); markRootPinged(root, suspendedLanes, eventTime); break; } // 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. root.timeoutHandle = scheduleTimeout( commitRoot.bind( null, root, workInProgressRootRecoverableErrors, workInProgressTransitions, ), msUntilTimeout, ); break; } } // The work expired. Commit immediately. commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, ); break; } case RootSuspendedWithDelay: { markRootSuspended(root, lanes); 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. break; } if (!shouldForceFlushFallbacksInDEV()) { // This is not a transition, but we did trigger an avoided state. // Schedule a placeholder to display after a short delay, using the Just // Noticeable Difference. // TODO: Is the JND optimization worth the added complexity? If this is // the only reason we track the event time, then probably not. // Consider removing. const mostRecentEventTime = getMostRecentEventTime(root, lanes); const eventTimeMs = mostRecentEventTime; const timeElapsedMs = now() - eventTimeMs; const msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; // Don't bother with a very short suspense time. if (msUntilTimeout > 10) { // Instead of committing the fallback immediately, wait for more data // to arrive. root.timeoutHandle = scheduleTimeout( commitRoot.bind( null, root, workInProgressRootRecoverableErrors, workInProgressTransitions, ), msUntilTimeout, ); break; } } // Commit the placeholder. commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, ); break; } case RootCompleted: { // The work completed. Ready to commit. commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, ); break; } default: { throw new Error('Unknown root exit status.'); } } } function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean { // 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. let node: Fiber = finishedWork; while (true) { if (node.flags & StoreConsistency) { const updateQueue: FunctionComponentUpdateQueue | null = (node.updateQueue: any); if (updateQueue !== null) { const checks = updateQueue.stores; if (checks !== null) { for (let i = 0; i < checks.length; i++) { const check = checks[i]; const getSnapshot = check.getSnapshot; const renderedValue = check.value; try { if (!is(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; } } } } } const child = node.child; if (node.subtreeFlags & StoreConsistency && child !== null) { child.return = node; node = child; continue; } if (node === finishedWork) { return true; } while (node.sibling === null) { if (node.return === null || node.return === finishedWork) { return true; } node = node.return; } node.sibling.return = node.return; node = node.sibling; } // Flow doesn't know this is unreachable, but eslint does // eslint-disable-next-line no-unreachable return true; } function markRootSuspended(root, suspendedLanes) { // When suspending, we should always exclude lanes that were pinged or (more // rarely, since we try to avoid it) updated during the render phase. // TODO: Lol maybe there's a better way to factor this besides this // obnoxiously named function :) suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes); suspendedLanes = removeLanes( suspendedLanes, workInProgressRootInterleavedUpdatedLanes, ); // $FlowFixMe[incompatible-call] found when upgrading Flow markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes); } // This is the entry point for synchronous tasks that don't go // through Scheduler function performSyncWorkOnRoot(root) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { syncNestedUpdateFlag(); } if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { throw new Error('Should not already be working.'); } flushPassiveEffects(); let lanes = getNextLanes(root, NoLanes); if (!includesSomeLane(lanes, SyncLane)) { // There's no remaining sync work left. ensureRootIsScheduled(root, now()); return null; } let exitStatus = renderRootSync(root, lanes); if (root.tag !== LegacyRoot && 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. const originallyAttemptedLanes = lanes; const errorRetryLanes = getLanesToRetrySynchronouslyOnError( root, originallyAttemptedLanes, ); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; exitStatus = recoverFromConcurrentError( root, originallyAttemptedLanes, errorRetryLanes, ); } } if (exitStatus === RootFatalErrored) { const fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); markRootSuspended(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; } 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); ensureRootIsScheduled(root, now()); return null; } // We now have a consistent tree. Because this is a sync render, we // will commit it even if something suspended. const finishedWork: Fiber = (root.current.alternate: any); root.finishedWork = finishedWork; root.finishedLanes = lanes; commitRoot( root, workInProgressRootRecoverableErrors, workInProgressTransitions, ); // Before exiting, make sure there's a callback scheduled for the next // pending level. ensureRootIsScheduled(root, now()); return null; } export function flushRoot(root: FiberRoot, lanes: Lanes) { if (lanes !== NoLanes) { markRootEntangled(root, mergeLanes(lanes, SyncLane)); ensureRootIsScheduled(root, now()); if ((executionContext & (RenderContext | CommitContext)) === NoContext) { resetRenderTimer(); flushSyncCallbacks(); } } } export function getExecutionContext(): ExecutionContext { return executionContext; } export function deferredUpdates(fn: () => A): A { const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DefaultEventPriority); return fn(); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; } } export function batchedUpdates(fn: A => R, a: A): R { const prevExecutionContext = executionContext; executionContext |= BatchedContext; try { return fn(a); } finally { executionContext = prevExecutionContext; // If there were legacy sync updates, flush them at the end of the outer // most batchedUpdates-like method. if ( executionContext === NoContext && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode. !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy) ) { resetRenderTimer(); flushSyncCallbacksOnlyInLegacyMode(); } } } export function discreteUpdates( fn: (A, B, C, D) => R, a: A, b: B, c: C, d: D, ): R { const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); return fn(a, b, c, d); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; if (executionContext === NoContext) { resetRenderTimer(); } } } // Overload the definition to the two valid signatures. // Warning, this opts-out of checking the function body. declare function flushSync(fn: () => R): R; // eslint-disable-next-line no-redeclare declare function flushSync(void): void; // eslint-disable-next-line no-redeclare export function flushSync(fn: (() => R) | void): R | void { // In legacy mode, we flush pending passive effects at the beginning of the // next event, not at the end of the previous one. if ( rootWithPendingPassiveEffects !== null && rootWithPendingPassiveEffects.tag === LegacyRoot && (executionContext & (RenderContext | CommitContext)) === NoContext ) { flushPassiveEffects(); } const prevExecutionContext = executionContext; executionContext |= BatchedContext; const prevTransition = ReactCurrentBatchConfig.transition; const previousPriority = getCurrentUpdatePriority(); try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); if (fn) { return fn(); } else { return undefined; } } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; executionContext = prevExecutionContext; // Flush the immediate callbacks that were scheduled during this batch. // Note that this will happen even if batchedUpdates is higher up // the stack. if ((executionContext & (RenderContext | CommitContext)) === NoContext) { flushSyncCallbacks(); } } } export function isAlreadyRendering(): boolean { // Used by the renderer to print a warning if certain APIs are called from // the wrong context. return ( __DEV__ && (executionContext & (RenderContext | CommitContext)) !== NoContext ); } export function isInvalidExecutionContextForEventFunction(): boolean { // Used to throw if certain APIs are called from the wrong context. return (executionContext & RenderContext) !== NoContext; } export function flushControlled(fn: () => mixed): void { const prevExecutionContext = executionContext; executionContext |= BatchedContext; const prevTransition = ReactCurrentBatchConfig.transition; const previousPriority = getCurrentUpdatePriority(); try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); fn(); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = prevTransition; executionContext = prevExecutionContext; if (executionContext === NoContext) { // Flush the immediate callbacks that were scheduled during this batch resetRenderTimer(); flushSyncCallbacks(); } } } // 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 export function setRenderLanes(subtreeRenderLanes: Lanes) { renderLanes = subtreeRenderLanes; } export function getRenderLanes(): Lanes { return renderLanes; } function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { root.finishedWork = null; root.finishedLanes = NoLanes; const timeoutHandle = root.timeoutHandle; 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 Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); } if (workInProgress !== null) { let interruptedWork = workInProgressIsSuspended ? workInProgress : workInProgress.return; while (interruptedWork !== null) { const current = interruptedWork.alternate; unwindInterruptedWork( current, interruptedWork, workInProgressRootRenderLanes, ); interruptedWork = interruptedWork.return; } resetWakeableStateAfterEachAttempt(); resetThenableStateOnCompletion(); } workInProgressRoot = root; const rootWorkInProgress = createWorkInProgress(root.current, null); workInProgress = rootWorkInProgress; workInProgressRootRenderLanes = renderLanes = lanes; workInProgressIsSuspended = false; workInProgressThrownValue = null; workInProgressRootDidAttachPingListener = false; workInProgressRootExitStatus = RootInProgress; workInProgressRootFatalError = null; workInProgressRootSkippedLanes = NoLanes; workInProgressRootInterleavedUpdatedLanes = NoLanes; workInProgressRootRenderPhaseUpdatedLanes = NoLanes; workInProgressRootPingedLanes = NoLanes; workInProgressRootConcurrentErrors = null; workInProgressRootRecoverableErrors = null; finishQueueingConcurrentUpdates(); if (__DEV__) { ReactStrictModeWarnings.discardPendingWarnings(); } return rootWorkInProgress; } function handleThrow(root, thrownValue): void { // Reset module-level state that was set during the render phase. resetContextDependencies(); resetHooksAfterThrow(); resetCurrentDebugFiberInDEV(); // TODO: I found and added this missing line while investigating a // separate issue. Write a regression test using string refs. ReactCurrentOwner.current = null; // Setting this to `true` tells the work loop to unwind the stack instead // of entering the begin phase. It's called "suspended" because it usually // happens because of Suspense, but it also applies to errors. Think of it // as suspending the execution of the work loop. workInProgressIsSuspended = true; workInProgressThrownValue = thrownValue; const erroredWork = workInProgress; if (erroredWork === null) { // This is a fatal error workInProgressRootExitStatus = RootFatalErrored; workInProgressRootFatalError = thrownValue; return; } if (enableProfilerTimer && 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); } if (enableSchedulingProfiler) { markComponentRenderStopped(); if ( thrownValue !== null && typeof thrownValue === 'object' && typeof thrownValue.then === 'function' ) { const wakeable: Wakeable = (thrownValue: any); markComponentSuspended( erroredWork, wakeable, workInProgressRootRenderLanes, ); } else { markComponentErrored( erroredWork, thrownValue, workInProgressRootRenderLanes, ); } } } function pushDispatcher(container) { prepareRendererToRender(container); const prevDispatcher = ReactCurrentDispatcher.current; ReactCurrentDispatcher.current = ContextOnlyDispatcher; 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; } } function popDispatcher(prevDispatcher) { resetRendererAfterRender(); ReactCurrentDispatcher.current = prevDispatcher; } function pushCacheDispatcher() { if (enableCache) { const prevCacheDispatcher = ReactCurrentCache.current; ReactCurrentCache.current = DefaultCacheDispatcher; return prevCacheDispatcher; } else { return null; } } function popCacheDispatcher(prevCacheDispatcher) { if (enableCache) { ReactCurrentCache.current = prevCacheDispatcher; } } export function markCommitTimeOfFallback() { globalMostRecentFallbackTime = now(); } export function markSkippedUpdateLanes(lane: Lane | Lanes): void { workInProgressRootSkippedLanes = mergeLanes( lane, workInProgressRootSkippedLanes, ); } export function renderDidSuspend(): void { if (workInProgressRootExitStatus === RootInProgress) { workInProgressRootExitStatus = RootSuspended; } } export function renderDidSuspendDelayIfPossible(): void { if ( workInProgressRootExitStatus === RootInProgress || workInProgressRootExitStatus === RootSuspended || workInProgressRootExitStatus === RootErrored ) { workInProgressRootExitStatus = RootSuspendedWithDelay; } // Check if there are updates that we skipped tree that might have unblocked // this render. if ( workInProgressRoot !== null && (includesNonIdleWork(workInProgressRootSkippedLanes) || includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) ) { // 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. markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes); } } export function renderDidError(error: CapturedValue) { if (workInProgressRootExitStatus !== RootSuspendedWithDelay) { workInProgressRootExitStatus = RootErrored; } 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. export function renderHasNotSuspendedYet(): boolean { // If something errored or completed, we can't really be sure, // so those are false. return workInProgressRootExitStatus === RootInProgress; } function renderRootSync(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; const prevDispatcher = pushDispatcher(root.containerInfo); const prevCacheDispatcher = pushCacheDispatcher(); // 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 (enableUpdaterTracking) { if (isDevToolsPresent) { const 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. movePendingFibersToMemoized(root, lanes); } } workInProgressTransitions = getTransitionsForLanes(root, lanes); prepareFreshStack(root, lanes); } if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); } } if (enableSchedulingProfiler) { markRenderStarted(lanes); } do { try { workLoopSync(); break; } catch (thrownValue) { handleThrow(root, thrownValue); } } while (true); resetContextDependencies(); executionContext = prevExecutionContext; popDispatcher(prevDispatcher); popCacheDispatcher(prevCacheDispatcher); 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.', ); } if (__DEV__) { if (enableDebugTracing) { logRenderStopped(); } } if (enableSchedulingProfiler) { markRenderStopped(); } // Set this to null to indicate there's no in-progress render. workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete. finishQueueingConcurrentUpdates(); return workInProgressRootExitStatus; } // The work loop is an extremely hot path. Tell Closure not to inline it. /** @noinline */ function workLoopSync() { // Perform work without checking if we need to yield between fiber. if (workInProgressIsSuspended) { // The current work-in-progress was already attempted. We need to unwind // it before we continue the normal work loop. const thrownValue = workInProgressThrownValue; workInProgressIsSuspended = false; workInProgressThrownValue = null; if (workInProgress !== null) { resumeSuspendedUnitOfWork(workInProgress, thrownValue); } } while (workInProgress !== null) { performUnitOfWork(workInProgress); } } function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { const prevExecutionContext = executionContext; executionContext |= RenderContext; const prevDispatcher = pushDispatcher(root.containerInfo); const prevCacheDispatcher = pushCacheDispatcher(); // 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 (enableUpdaterTracking) { if (isDevToolsPresent) { const 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. movePendingFibersToMemoized(root, lanes); } } workInProgressTransitions = getTransitionsForLanes(root, lanes); resetRenderTimer(); prepareFreshStack(root, lanes); } if (__DEV__) { if (enableDebugTracing) { logRenderStarted(lanes); } } if (enableSchedulingProfiler) { markRenderStarted(lanes); } do { try { workLoopConcurrent(); break; } catch (thrownValue) { handleThrow(root, thrownValue); if (isTrackingSuspendedThenable()) { // 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. break; } } } while (true); resetContextDependencies(); popDispatcher(prevDispatcher); popCacheDispatcher(prevCacheDispatcher); executionContext = prevExecutionContext; if (__DEV__) { if (enableDebugTracing) { logRenderStopped(); } } // Check if the tree has completed. if (workInProgress !== null) { // Still work remaining. if (enableSchedulingProfiler) { markRenderYielded(); } return RootInProgress; } else { // Completed the tree. if (enableSchedulingProfiler) { markRenderStopped(); } // Set this to null to indicate there's no in-progress render. workInProgressRoot = null; workInProgressRootRenderLanes = NoLanes; // It's safe to process the queue now that the render phase is complete. finishQueueingConcurrentUpdates(); // Return the final exit status. return workInProgressRootExitStatus; } } /** @noinline */ function workLoopConcurrent() { // Perform work until Scheduler asks us to yield if (workInProgressIsSuspended) { // The current work-in-progress was already attempted. We need to unwind // it before we continue the normal work loop. const thrownValue = workInProgressThrownValue; workInProgressIsSuspended = false; workInProgressThrownValue = null; if (workInProgress !== null) { resumeSuspendedUnitOfWork(workInProgress, thrownValue); } } while (workInProgress !== null && !shouldYield()) { // $FlowFixMe[incompatible-call] found when upgrading Flow performUnitOfWork(workInProgress); } } function performUnitOfWork(unitOfWork: Fiber): void { // 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. const current = unitOfWork.alternate; setCurrentDebugFiberInDEV(unitOfWork); let next; if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, renderLanes); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); } else { next = beginWork(current, unitOfWork, renderLanes); } resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. completeUnitOfWork(unitOfWork); } else { workInProgress = next; } ReactCurrentOwner.current = null; } function resumeSuspendedUnitOfWork( unitOfWork: Fiber, thrownValue: mixed, ): void { // This is a fork of performUnitOfWork specifcally for resuming a fiber that // just suspended. In some cases, we may choose to retry the fiber immediately // instead of unwinding the stack. It's a separate function to keep the // additional logic out of the work loop's hot path. const wasPinged = suspendedThenableDidResolve(); resetWakeableStateAfterEachAttempt(); if (!wasPinged) { // The thenable wasn't pinged. Return to the normal work loop. This will // unwind the stack, and potentially result in showing a fallback. resetThenableStateOnCompletion(); const returnFiber = unitOfWork.return; if (returnFiber === null || workInProgressRoot === null) { // Expected to be working on a non-root fiber. This is a fatal error // because there's no ancestor that can handle it; the root is // supposed to capture all errors that weren't caught by an error // boundary. workInProgressRootExitStatus = RootFatalErrored; workInProgressRootFatalError = thrownValue; // 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. workInProgress = null; return; } try { // Find and mark the nearest Suspense or error boundary that can handle // this "exception". throwException( workInProgressRoot, returnFiber, unitOfWork, thrownValue, workInProgressRootRenderLanes, ); } 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. workInProgress = returnFiber; throw error; } // Return to the normal work loop. completeUnitOfWork(unitOfWork); return; } // The work-in-progress was immediately pinged. Instead of unwinding the // stack and potentially showing a fallback, unwind only the last stack frame, // reset the fiber, and try rendering it again. const current = unitOfWork.alternate; unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes); unitOfWork = workInProgress = resetWorkInProgress(unitOfWork, renderLanes); setCurrentDebugFiberInDEV(unitOfWork); let next; if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, renderLanes); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); } else { next = beginWork(current, unitOfWork, renderLanes); } // The begin phase finished successfully without suspending. Reset the state // used to track the fiber while it was suspended. Then return to the normal // work loop. resetThenableStateOnCompletion(); resetCurrentDebugFiberInDEV(); unitOfWork.memoizedProps = unitOfWork.pendingProps; if (next === null) { // If this doesn't spawn new work, complete the current work. completeUnitOfWork(unitOfWork); } else { workInProgress = next; } ReactCurrentOwner.current = null; } function completeUnitOfWork(unitOfWork: Fiber): void { // 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. let completedWork: Fiber = 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. const current = completedWork.alternate; const returnFiber = completedWork.return; // Check if the work completed or if something threw. if ((completedWork.flags & Incomplete) === NoFlags) { setCurrentDebugFiberInDEV(completedWork); let next; if ( !enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode ) { next = completeWork(current, completedWork, renderLanes); } else { startProfilerTimer(completedWork); next = completeWork(current, completedWork, renderLanes); // Update render duration assuming we didn't error. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); } resetCurrentDebugFiberInDEV(); if (next !== null) { // Completing this fiber spawned new work. Work on that next. workInProgress = next; return; } } else { // 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. const next = unwindWork(current, completedWork, renderLanes); // Because this fiber did not complete, don't reset its lanes. if (next !== null) { // If completing this work spawned new work, do that next. We'll come // back here again. // Since we're restarting, remove anything that is not a host effect // from the effect tag. next.flags &= HostEffectMask; workInProgress = next; return; } if ( enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode ) { // Record the render duration for the fiber that errored. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing. let actualDuration = completedWork.actualDuration; let child = completedWork.child; while (child !== null) { // $FlowFixMe[unsafe-addition] addition with possible null/undefined value actualDuration += child.actualDuration; child = child.sibling; } completedWork.actualDuration = actualDuration; } if (returnFiber !== null) { // Mark the parent fiber as incomplete and clear its subtree flags. returnFiber.flags |= Incomplete; returnFiber.subtreeFlags = NoFlags; returnFiber.deletions = null; } else { // We've unwound all the way to the root. workInProgressRootExitStatus = RootDidNotComplete; workInProgress = null; return; } } const siblingFiber = completedWork.sibling; 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 completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork !== null); // We've reached the root. if (workInProgressRootExitStatus === RootInProgress) { workInProgressRootExitStatus = RootCompleted; } } function commitRoot( root: FiberRoot, recoverableErrors: null | Array>, transitions: Array | null, ) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. const previousUpdateLanePriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); commitRootImpl( root, recoverableErrors, transitions, previousUpdateLanePriority, ); } finally { ReactCurrentBatchConfig.transition = prevTransition; setCurrentUpdatePriority(previousUpdateLanePriority); } return null; } function commitRootImpl( root: FiberRoot, recoverableErrors: null | Array>, transitions: Array | null, renderPriorityLevel: EventPriority, ) { 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); flushRenderPhaseStrictModeWarningsInDEV(); if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { throw new Error('Should not already be working.'); } const finishedWork = root.finishedWork; const lanes = root.finishedLanes; if (__DEV__) { if (enableDebugTracing) { logCommitStarted(lanes); } } if (enableSchedulingProfiler) { markCommitStarted(lanes); } if (finishedWork === null) { if (__DEV__) { if (enableDebugTracing) { logCommitStopped(); } } if (enableSchedulingProfiler) { markCommitStopped(); } return null; } else { if (__DEV__) { if (lanes === NoLanes) { console.error( 'root.finishedLanes should not be empty during a commit. This is a ' + 'bug in React.', ); } } } root.finishedWork = null; root.finishedLanes = NoLanes; 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. root.callbackNode = null; root.callbackPriority = NoLane; // Check which lanes no longer have any work scheduled on them, and mark // those as finished. let 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. const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes(); remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); markRootFinished(root, remainingLanes); if (root === workInProgressRoot) { // We can reset these now that they are finished. workInProgressRoot = null; workInProgress = null; workInProgressRootRenderLanes = NoLanes; } else { // This indicates that the last root we worked on is not the same one that // we're committing now. This most commonly happens when a suspended root // times out. } // 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. if ( (finishedWork.subtreeFlags & PassiveMask) !== NoFlags || (finishedWork.flags & PassiveMask) !== NoFlags ) { 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 pendingPassiveTransitions = transitions; scheduleCallback(NormalSchedulerPriority, () => { 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) 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. const subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; const rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; if (subtreeHasEffects || rootHasEffect) { const prevTransition = ReactCurrentBatchConfig.transition; ReactCurrentBatchConfig.transition = null; const previousPriority = getCurrentUpdatePriority(); setCurrentUpdatePriority(DiscreteEventPriority); const prevExecutionContext = executionContext; executionContext |= CommitContext; // Reset this to null before calling lifecycles ReactCurrentOwner.current = 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. const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects( root, finishedWork, ); if (enableProfilerTimer) { // Mark the current commit time to be shared by all Profilers in this // batch. This enables them to be grouped later. recordCommitTime(); } if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { // Track the root here, rather than in commitLayoutEffects(), because of ref setters. // Updates scheduled during ref detachment should also be flagged. rootCommittingMutationOrLayoutEffects = root; } // The next phase is the mutation phase, where we mutate the host tree. commitMutationEffects(root, finishedWork, lanes); if (enableCreateEventHandleAPI) { if (shouldFireAfterActiveInstanceBlur) { afterActiveInstanceBlur(); } } resetAfterCommit(root.containerInfo); // 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. root.current = finishedWork; // The next phase is the layout phase, where we call effects that read // the host tree after it's been mutated. The idiomatic use case for this is // layout, but class component lifecycles also fire here for legacy reasons. if (__DEV__) { if (enableDebugTracing) { logLayoutEffectsStarted(lanes); } } if (enableSchedulingProfiler) { markLayoutEffectsStarted(lanes); } commitLayoutEffects(finishedWork, root, lanes); if (__DEV__) { if (enableDebugTracing) { logLayoutEffectsStopped(); } } if (enableSchedulingProfiler) { markLayoutEffectsStopped(); } if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) { rootCommittingMutationOrLayoutEffects = null; } // Tell Scheduler to yield at the end of the frame, so the browser has an // opportunity to paint. requestPaint(); executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value. setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = 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. if (enableProfilerTimer) { recordCommitTime(); } } const rootDidHavePassiveEffects = rootDoesHavePassiveEffects; 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); if (__DEV__) { nestedPassiveUpdateCount = 0; rootWithPassiveNestedUpdates = null; } } // Read this again, since an effect might have updated it 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. if (remainingLanes === NoLanes) { // If there's no remaining work, we can clear the set of already failed // error boundaries. legacyErrorBoundariesThatAlreadyFailed = null; } if (__DEV__) { if (!rootDidHavePassiveEffects) { commitDoubleInvokeEffectsInDEV(root, false); } } onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel); if (enableUpdaterTracking) { if (isDevToolsPresent) { root.memoizedUpdaters.clear(); } } if (__DEV__) { onCommitRootTestSelector(); } // Always call this before exiting `commitRoot`, to ensure that any // additional work on this root is scheduled. ensureRootIsScheduled(root, now()); 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. const onRecoverableError = root.onRecoverableError; for (let i = 0; i < recoverableErrors.length; i++) { const recoverableError = recoverableErrors[i]; const errorInfo = makeErrorInfo( recoverableError.digest, recoverableError.stack, ); onRecoverableError(recoverableError.value, errorInfo); } } if (hasUncaughtError) { hasUncaughtError = false; const error = firstUncaughtError; firstUncaughtError = null; throw error; } // 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. if ( includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && root.tag !== LegacyRoot ) { flushPassiveEffects(); } // Read this again, since a passive effect might have updated it remainingLanes = root.pendingLanes; if (includesSomeLane(remainingLanes, (SyncLane: Lane))) { if (enableProfilerTimer && enableProfilerNestedUpdatePhase) { 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 (root === rootWithNestedUpdates) { nestedUpdateCount++; } else { nestedUpdateCount = 0; rootWithNestedUpdates = root; } } else { nestedUpdateCount = 0; } // If layout work was scheduled, flush it now. flushSyncCallbacks(); if (__DEV__) { if (enableDebugTracing) { logCommitStopped(); } } if (enableSchedulingProfiler) { markCommitStopped(); } if (enableTransitionTracing) { // We process transitions during passive effects. However, passive effects can be // processed synchronously during the commit phase as well as asynchronously after // paint. At the end of the commit phase, we schedule a callback that will be called // after the next paint. If the transitions have already been processed (passive // effect phase happened synchronously), we will schedule a callback to process // the transitions. However, if we don't have any pending transition callbacks, this // means that the transitions have yet to be processed (passive effects processed after paint) // so we will store the end time of paint so that we can process the transitions // and then call the callback via the correct end time. const prevRootTransitionCallbacks = root.transitionCallbacks; if (prevRootTransitionCallbacks !== null) { schedulePostPaintCallback(endTime => { const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks; if (prevPendingTransitionCallbacks !== null) { currentPendingTransitionCallbacks = null; scheduleCallback(IdleSchedulerPriority, () => { processTransitionCallbacks( prevPendingTransitionCallbacks, endTime, prevRootTransitionCallbacks, ); }); } else { currentEndTime = endTime; } }); } } return null; } function makeErrorInfo(digest: ?string, componentStack: ?string) { if (__DEV__) { const errorInfo = { componentStack, digest, }; Object.defineProperty(errorInfo, 'digest', { configurable: false, enumerable: true, get() { console.error( 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' + ' This property is deprecated and will be removed in a future version of React.' + ' To access the digest of an Error look for this property on the Error instance itself.', ); return digest; }, }); return errorInfo; } else { return { digest, componentStack, }; } } function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { if (enableCache) { const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes); if (pooledCacheLanes === NoLanes) { // None of the remaining work relies on the cache pool. Clear it so // subsequent requests get a new cache const pooledCache = root.pooledCache; if (pooledCache != null) { root.pooledCache = null; releaseCache(pooledCache); } } } } export function flushPassiveEffects(): boolean { // 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 const 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 const remainingLanes = pendingPassiveEffectsRemainingLanes; pendingPassiveEffectsRemainingLanes = NoLanes; const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes); const priority = lowerEventPriority(DefaultEventPriority, renderPriority); const prevTransition = ReactCurrentBatchConfig.transition; const previousPriority = getCurrentUpdatePriority(); try { ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(priority); return flushPassiveEffectsImpl(); } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig.transition = 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); } } return false; } export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void { if (enableProfilerTimer && enableProfilerCommitHooks) { pendingPassiveProfilerEffects.push(fiber); if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); } } } function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; } // Cache and clear the transitions flag const transitions = pendingPassiveTransitions; pendingPassiveTransitions = null; const root = rootWithPendingPassiveEffects; const 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. pendingPassiveEffectsLanes = NoLanes; if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { throw new Error('Cannot flush passive effects while already rendering.'); } if (__DEV__) { isFlushingPassiveEffects = true; didScheduleUpdateDuringPassiveEffects = false; if (enableDebugTracing) { logPassiveEffectsStarted(lanes); } } if (enableSchedulingProfiler) { markPassiveEffectsStarted(lanes); } const prevExecutionContext = executionContext; executionContext |= CommitContext; commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current, lanes, transitions); // TODO: Move to commitPassiveMountEffects if (enableProfilerTimer && enableProfilerCommitHooks) { const profilerEffects = pendingPassiveProfilerEffects; pendingPassiveProfilerEffects = []; for (let i = 0; i < profilerEffects.length; i++) { const fiber = ((profilerEffects[i]: any): Fiber); commitPassiveEffectDurations(root, fiber); } } if (__DEV__) { if (enableDebugTracing) { logPassiveEffectsStopped(); } } if (enableSchedulingProfiler) { markPassiveEffectsStopped(); } if (__DEV__) { commitDoubleInvokeEffectsInDEV(root, true); } executionContext = prevExecutionContext; flushSyncCallbacks(); if (enableTransitionTracing) { const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks; const prevRootTransitionCallbacks = root.transitionCallbacks; const prevEndTime = currentEndTime; if ( prevPendingTransitionCallbacks !== null && prevRootTransitionCallbacks !== null && prevEndTime !== null ) { currentPendingTransitionCallbacks = null; currentEndTime = null; scheduleCallback(IdleSchedulerPriority, () => { processTransitionCallbacks( prevPendingTransitionCallbacks, prevEndTime, prevRootTransitionCallbacks, ); }); } } if (__DEV__) { // 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; } } else { nestedPassiveUpdateCount = 0; } isFlushingPassiveEffects = false; didScheduleUpdateDuringPassiveEffects = false; } // TODO: Move to commitPassiveMountEffects onPostCommitRootDevTools(root); if (enableProfilerTimer && enableProfilerCommitHooks) { const stateNode = root.current.stateNode; stateNode.effectDuration = 0; stateNode.passiveEffectDuration = 0; } return true; } export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean { return ( legacyErrorBoundariesThatAlreadyFailed !== null && legacyErrorBoundariesThatAlreadyFailed.has(instance) ); } export function markLegacyErrorBoundaryAsFailed(instance: mixed) { if (legacyErrorBoundariesThatAlreadyFailed === null) { legacyErrorBoundariesThatAlreadyFailed = new Set([instance]); } else { legacyErrorBoundariesThatAlreadyFailed.add(instance); } } function prepareToThrowUncaughtError(error: mixed) { if (!hasUncaughtError) { hasUncaughtError = true; firstUncaughtError = error; } } export const onUncaughtError = prepareToThrowUncaughtError; function captureCommitPhaseErrorOnRoot( rootFiber: Fiber, sourceFiber: Fiber, error: mixed, ) { const errorInfo = createCapturedValueAtFiber(error, sourceFiber); const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane)); const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); } } export function captureCommitPhaseError( sourceFiber: Fiber, nearestMountedAncestor: Fiber | null, error: mixed, ) { if (__DEV__) { reportUncaughtErrorInDEV(error); setIsRunningInsertionEffect(false); } 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); return; } let fiber = null; if (skipUnmountedBoundaries) { fiber = nearestMountedAncestor; } else { fiber = sourceFiber.return; } while (fiber !== null) { if (fiber.tag === HostRoot) { captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error); return; } else if (fiber.tag === ClassComponent) { const ctor = fiber.type; const instance = fiber.stateNode; if ( typeof ctor.getDerivedStateFromError === 'function' || (typeof instance.componentDidCatch === 'function' && !isAlreadyFailedLegacyErrorBoundary(instance)) ) { const errorInfo = createCapturedValueAtFiber(error, sourceFiber); const update = createClassErrorUpdate( fiber, errorInfo, (SyncLane: Lane), ); const root = enqueueUpdate(fiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); } return; } } fiber = fiber.return; } if (__DEV__) { // TODO: Until we re-land skipUnmountedBoundaries (see #20147), this warning // will fire for errors that are thrown by destroy functions inside deleted // trees. What it should instead do is propagate the error to the parent of // the deleted tree. In the meantime, do not add this warning to the // allowlist; this is only for our internal use. console.error( 'Internal React error: Attempted to capture a commit phase error ' + 'inside a detached tree. This indicates a bug in React. Likely ' + '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, ); } } export function attachPingListener( root: FiberRoot, wakeable: Wakeable, lanes: 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. let pingCache = root.pingCache; let threadIDs; if (pingCache === null) { pingCache = root.pingCache = new PossiblyWeakMap(); threadIDs = new Set(); pingCache.set(wakeable, threadIDs); } else { threadIDs = pingCache.get(wakeable); if (threadIDs === undefined) { threadIDs = new Set(); pingCache.set(wakeable, threadIDs); } } if (!threadIDs.has(lanes)) { workInProgressRootDidAttachPingListener = true; // Memoize using the thread ID to prevent redundant listeners. threadIDs.add(lanes); const ping = pingSuspendedRoot.bind(null, root, wakeable, lanes); if (enableUpdaterTracking) { if (isDevToolsPresent) { // If we have pending work still, restore the original updaters restorePendingUpdaters(root, lanes); } } wakeable.then(ping, ping); } } function pingSuspendedRoot( root: FiberRoot, wakeable: Wakeable, pingedLanes: Lanes, ) { const pingCache = root.pingCache; if (pingCache !== null) { // The wakeable resolved, so we no longer need to memoize, because it will // never be thrown again. pingCache.delete(wakeable); } const eventTime = requestEventTime(); markRootPinged(root, pingedLanes, eventTime); warnIfSuspenseResolutionNotWrappedWithActDEV(root); 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() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) ) { // Restart from the root. 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, ); } } ensureRootIsScheduled(root, eventTime); } function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { // 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(boundaryFiber); } // TODO: Special case idle priority? const eventTime = requestEventTime(); const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane); if (root !== null) { markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); } } export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) { const suspenseState: null | SuspenseState = boundaryFiber.memoizedState; let retryLane = NoLane; if (suspenseState !== null) { retryLane = suspenseState.retryLane; } retryTimedOutBoundary(boundaryFiber, retryLane); } export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) { let retryLane = NoLane; // Default let retryCache: WeakSet | Set | null; switch (boundaryFiber.tag) { case SuspenseComponent: retryCache = boundaryFiber.stateNode; const suspenseState: null | SuspenseState = boundaryFiber.memoizedState; if (suspenseState !== null) { retryLane = suspenseState.retryLane; } break; case SuspenseListComponent: retryCache = boundaryFiber.stateNode; break; case OffscreenComponent: { const instance: OffscreenInstance = boundaryFiber.stateNode; // $FlowFixMe[incompatible-type] found when upgrading Flow retryCache = instance._retryCache; break; } default: throw new Error( 'Pinged unknown suspense boundary type. ' + 'This is probably a bug in React.', ); } 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); } // Computes the next Just Noticeable Difference (JND) boundary. // The theory is that a person can't tell the difference between small differences in time. // Therefore, if we wait a bit longer than necessary that won't translate to a noticeable // difference in the experience. However, waiting for longer might mean that we can avoid // showing an intermediate loading state. The longer we have already waited, the harder it // is to tell small differences in time. Therefore, the longer we've already waited, // the longer we can wait additionally. At some point we have to give up though. // We pick a train model where the next boundary commits at a consistent schedule. // These particular numbers are vague estimates. We expect to adjust them based on research. function jnd(timeElapsed: number) { return timeElapsed < 120 ? 120 : timeElapsed < 480 ? 480 : timeElapsed < 1080 ? 1080 : timeElapsed < 1920 ? 1920 : timeElapsed < 3000 ? 3000 : timeElapsed < 4320 ? 4320 : ceil(timeElapsed / 1960) * 1960; } export function throwIfInfiniteUpdateLoopDetected() { if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { nestedUpdateCount = 0; nestedPassiveUpdateCount = 0; rootWithNestedUpdates = null; rootWithPassiveNestedUpdates = null; 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 (__DEV__) { if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) { nestedPassiveUpdateCount = 0; rootWithPassiveNestedUpdates = null; console.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.', ); } } } function flushRenderPhaseStrictModeWarningsInDEV() { if (__DEV__) { ReactStrictModeWarnings.flushLegacyContextWarning(); if (warnAboutDeprecatedLifecycles) { ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings(); } } } function recursivelyTraverseAndDoubleInvokeEffectsInDEV( root: FiberRoot, parentFiber: Fiber, isInStrictMode: boolean, ) { if ((parentFiber.subtreeFlags & (PlacementDEV | Visibility)) === NoFlags) { // Parent's descendants have already had effects double invoked. // Early exit to avoid unnecessary tree traversal. return; } let child = parentFiber.child; while (child !== null) { doubleInvokeEffectsInDEVIfNecessary(root, child, isInStrictMode); child = child.sibling; } } // Unconditionally disconnects and connects passive and layout effects. function doubleInvokeEffectsOnFiber(root: FiberRoot, fiber: Fiber) { disappearLayoutEffects(fiber); disconnectPassiveEffect(fiber); reappearLayoutEffects(root, fiber.alternate, fiber, false); reconnectPassiveEffects(root, fiber, NoLanes, null, false); } function doubleInvokeEffectsInDEVIfNecessary( root: FiberRoot, fiber: Fiber, parentIsInStrictMode: boolean, ) { const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE; const isInStrictMode = parentIsInStrictMode || isStrictModeFiber; // First case: the fiber **is not** of type OffscreenComponent. No // special rules apply to double invoking effects. if (fiber.tag !== OffscreenComponent) { if (fiber.flags & PlacementDEV) { setCurrentDebugFiberInDEV(fiber); if (isInStrictMode) { doubleInvokeEffectsOnFiber(root, fiber); } resetCurrentDebugFiberInDEV(); } else { recursivelyTraverseAndDoubleInvokeEffectsInDEV( root, fiber, isInStrictMode, ); } return; } // Second case: the fiber **is** of type OffscreenComponent. // This branch contains cases specific to Offscreen. if (fiber.memoizedState === null) { // Only consider Offscreen that is visible. // TODO (Offscreen) Handle manual mode. setCurrentDebugFiberInDEV(fiber); 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, ); } resetCurrentDebugFiberInDEV(); } } function commitDoubleInvokeEffectsInDEV( root: FiberRoot, hasPassiveEffects: boolean, ) { if (__DEV__) { if (useModernStrictMode) { let doubleInvokeEffects = true; if (root.tag === LegacyRoot && !(root.current.mode & StrictLegacyMode)) { doubleInvokeEffects = false; } if ( root.tag === ConcurrentRoot && !(root.current.mode & (StrictLegacyMode | StrictEffectsMode)) ) { doubleInvokeEffects = false; } recursivelyTraverseAndDoubleInvokeEffectsInDEV( root, root.current, doubleInvokeEffects, ); } else { legacyCommitDoubleInvokeEffectsInDEV(root.current, hasPassiveEffects); } } } function legacyCommitDoubleInvokeEffectsInDEV( fiber: Fiber, hasPassiveEffects: boolean, ) { // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level. // Maybe not a big deal since this is DEV only behavior. setCurrentDebugFiberInDEV(fiber); invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV); if (hasPassiveEffects) { invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV); } invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV); if (hasPassiveEffects) { invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV); } resetCurrentDebugFiberInDEV(); } function invokeEffectsInDev( firstChild: Fiber, fiberFlags: Flags, invokeEffectFn: (fiber: Fiber) => void, ) { let current: null | Fiber = firstChild; let subtreeRoot = null; while (current != null) { const primarySubtreeFlag = current.subtreeFlags & fiberFlags; if ( current !== subtreeRoot && current.child != null && primarySubtreeFlag !== NoFlags ) { current = current.child; } else { if ((current.flags & fiberFlags) !== NoFlags) { invokeEffectFn(current); } if (current.sibling !== null) { current = current.sibling; } else { current = subtreeRoot = current.return; } } } } let didWarnStateUpdateForNotYetMountedComponent: Set | null = null; export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) { if (__DEV__) { if ((executionContext & RenderContext) !== NoContext) { // We let the other warning about render phase updates deal with this one. return; } if (!(fiber.mode & ConcurrentMode)) { return; } const tag = fiber.tag; if ( tag !== IndeterminateComponent && 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. const componentName = getComponentNameFromFiber(fiber) || 'ReactComponent'; if (didWarnStateUpdateForNotYetMountedComponent !== null) { if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) { return; } // $FlowFixMe[incompatible-use] found when upgrading Flow didWarnStateUpdateForNotYetMountedComponent.add(componentName); } else { didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]); } const previousFiber = ReactCurrentFiberCurrent; try { setCurrentDebugFiberInDEV(fiber); console.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) { setCurrentDebugFiberInDEV(fiber); } else { resetCurrentDebugFiberInDEV(); } } } } let beginWork; if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { const dummyFiber = null; beginWork = (current, unitOfWork, lanes) => { // If a component throws an error, we replay it again in a synchronously // dispatched event, so that the debugger will treat it as an uncaught // error See ReactErrorUtils for more information. // Before entering the begin phase, copy the work-in-progress onto a dummy // fiber. If beginWork throws, we'll use this to reset the state. const originalWorkInProgressCopy = assignFiberPropertiesInDEV( dummyFiber, unitOfWork, ); try { return originalBeginWork(current, unitOfWork, lanes); } catch (originalError) { if ( didSuspendOrErrorWhileHydratingDEV() || (originalError !== null && typeof originalError === 'object' && typeof originalError.then === 'function') ) { // Don't replay promises. // Don't replay errors if we are hydrating and have already suspended or handled an error throw originalError; } // Keep this code in sync with handleThrow; any changes here must have // corresponding changes there. resetContextDependencies(); resetHooksAfterThrow(); // Don't reset current debug fiber, since we're about to work on the // same fiber again. // Unwind the failed stack frame unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes); // Restore the original properties of the fiber. assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy); if (enableProfilerTimer && unitOfWork.mode & ProfileMode) { // Reset the profiler timer. startProfilerTimer(unitOfWork); } // Run beginWork again. invokeGuardedCallback( null, originalBeginWork, null, current, unitOfWork, lanes, ); if (hasCaughtError()) { const replayError = clearCaughtError(); if ( typeof replayError === 'object' && replayError !== null && replayError._suppressLogging && typeof originalError === 'object' && originalError !== null && !originalError._suppressLogging ) { // If suppressed, let the flag carry over to the original error which is the one we'll rethrow. originalError._suppressLogging = true; } } // We always throw the original error in case the second render pass is not idempotent. // This can happen if a memoized function or CommonJS module doesn't throw after first invocation. throw originalError; } }; } else { beginWork = originalBeginWork; } let didWarnAboutUpdateInRender = false; let didWarnAboutUpdateInRenderForAnotherComponent; if (__DEV__) { didWarnAboutUpdateInRenderForAnotherComponent = new Set(); } function warnAboutRenderPhaseUpdatesInDEV(fiber) { if (__DEV__) { if ( ReactCurrentDebugFiberIsRenderingInDEV && !getIsUpdatingOpaqueValueInRenderPhaseInDEV() ) { switch (fiber.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: { const renderingComponentName = (workInProgress && getComponentNameFromFiber(workInProgress)) || 'Unknown'; // Dedupe by the rendering component because it's the one that needs to be fixed. const dedupeKey = renderingComponentName; if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) { didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey); const setStateComponentName = getComponentNameFromFiber(fiber) || 'Unknown'; console.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://reactjs.org/link/setstate-in-render', setStateComponentName, renderingComponentName, renderingComponentName, ); } break; } case ClassComponent: { if (!didWarnAboutUpdateInRender) { console.error( 'Cannot update during an existing state transition (such as ' + 'within `render`). Render methods should be a pure ' + 'function of props and state.', ); didWarnAboutUpdateInRender = true; } break; } } } } } export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void { if (enableUpdaterTracking) { if (isDevToolsPresent) { const memoizedUpdaters = root.memoizedUpdaters; memoizedUpdaters.forEach(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). } } } const fakeActCallbackNode = {}; function scheduleCallback(priorityLevel, callback) { if (__DEV__) { // If we're currently inside an `act` scope, bypass Scheduler and push to // the `act` queue instead. const actQueue = ReactCurrentActQueue.current; if (actQueue !== null) { actQueue.push(callback); return fakeActCallbackNode; } else { return Scheduler_scheduleCallback(priorityLevel, callback); } } else { // In production, always call Scheduler. This function will be stripped out. return Scheduler_scheduleCallback(priorityLevel, callback); } } function cancelCallback(callbackNode) { if (__DEV__ && callbackNode === fakeActCallbackNode) { return; } // In production, always call Scheduler. This function will be stripped out. return Scheduler_cancelCallback(callbackNode); } function shouldForceFlushFallbacksInDEV() { // Never force flush in production. This function should get stripped out. return __DEV__ && ReactCurrentActQueue.current !== null; } function warnIfUpdatesNotWrappedWithActDEV(fiber: Fiber): void { if (__DEV__) { if (fiber.mode & ConcurrentMode) { if (!isConcurrentActEnvironment()) { // Not in an act environment. No need to warn. return; } } else { // Legacy mode has additional cases where we suppress a warning. if (!isLegacyActEnvironment(fiber)) { // Not in an act environment. No need to warn. return; } if (executionContext !== NoContext) { // Legacy mode doesn't warn if the update is batched, i.e. // batchedUpdates or flushSync. return; } if ( fiber.tag !== FunctionComponent && fiber.tag !== ForwardRef && fiber.tag !== SimpleMemoComponent ) { // For backwards compatibility with pre-hooks code, legacy mode only // warns for updates that originate from a hook. return; } } if (ReactCurrentActQueue.current === null) { const previousFiber = ReactCurrentFiberCurrent; try { setCurrentDebugFiberInDEV(fiber); console.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://reactjs.org/link/wrap-tests-with-act', getComponentNameFromFiber(fiber), ); } finally { if (previousFiber) { setCurrentDebugFiberInDEV(fiber); } else { resetCurrentDebugFiberInDEV(); } } } } } function warnIfSuspenseResolutionNotWrappedWithActDEV(root: FiberRoot): void { if (__DEV__) { if ( root.tag !== LegacyRoot && isConcurrentActEnvironment() && ReactCurrentActQueue.current === null ) { console.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://reactjs.org/link/wrap-tests-with-act', ); } } } export function setIsRunningInsertionEffect(isRunning: boolean): void { if (__DEV__) { isRunningInsertionEffect = isRunning; } }