packages/react-reconciler/src/ReactFiberHooks.js JAVASCRIPT 5,266 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,266.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {11  ReactContext,12  StartTransitionOptions,13  Usable,14  Thenable,15  RejectedThenable,16  Awaited,17} from 'shared/ReactTypes';18import type {19  Fiber,20  FiberRoot,21  Dispatcher,22  HookType,23  MemoCache,24} from './ReactInternalTypes';25import type {Lanes, Lane} from './ReactFiberLane';26import type {HookFlags} from './ReactHookEffectTags';27import type {Flags} from './ReactFiberFlags';28import type {TransitionStatus} from './ReactFiberConfig';29import type {ScheduledGesture} from './ReactFiberGestureScheduler';3031import {32  HostTransitionContext,33  NotPendingTransition as NoPendingHostTransition,34  setCurrentUpdatePriority,35  getCurrentUpdatePriority,36} from './ReactFiberConfig';37import ReactSharedInternals from 'shared/ReactSharedInternals';38import {39  enableSchedulingProfiler,40  enableTransitionTracing,41  enableLegacyCache,42  disableLegacyMode,43  enableNoCloningMemoCache,44  enableViewTransition,45  enableGestureTransition,46} from 'shared/ReactFeatureFlags';47import {48  REACT_CONTEXT_TYPE,49  REACT_RECOVERABLE_TYPE,50  REACT_MEMO_CACHE_SENTINEL,51} from 'shared/ReactSymbols';5253import {54  NoMode,55  ConcurrentMode,56  StrictEffectsMode,57  StrictLegacyMode,58} from './ReactTypeOfMode';59import {60  NoLane,61  SyncLane,62  OffscreenLane,63  DeferredLane,64  NoLanes,65  isSubsetOfLanes,66  includesBlockingLane,67  includesOnlyNonUrgentLanes,68  mergeLanes,69  removeLanes,70  intersectLanes,71  isTransitionLane,72  markRootEntangled,73  includesSomeLane,74  isGestureRender,75  GestureLane,76  UpdateLanes,77} from './ReactFiberLane';78import {79  ContinuousEventPriority,80  higherEventPriority,81} from './ReactEventPriorities';82import {readContext, checkIfContextChanged} from './ReactFiberNewContext';83import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';84import {85  LayoutStatic as LayoutStaticEffect,86  Passive as PassiveEffect,87  PassiveStatic as PassiveStaticEffect,88  StaticMask as StaticMaskEffect,89  Update as UpdateEffect,90  StoreConsistency,91  MountLayoutDev as MountLayoutDevEffect,92  MountPassiveDev as MountPassiveDevEffect,93  FormReset,94} from './ReactFiberFlags';95import {96  NoFlags as HookNoFlags,97  HasEffect as HookHasEffect,98  Layout as HookLayout,99  Passive as HookPassive,100  Insertion as HookInsertion,101} from './ReactHookEffectTags';102import {103  getWorkInProgressRoot,104  getWorkInProgressRootRenderLanes,105  scheduleUpdateOnFiber,106  requestUpdateLane,107  requestDeferredLane,108  markSkippedUpdateLanes,109  isInvalidExecutionContextForEventFunction,110} from './ReactFiberWorkLoop';111112import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';113import is from 'shared/objectIs';114import isArray from 'shared/isArray';115import {116  markWorkInProgressReceivedUpdate,117  checkIfWorkInProgressReceivedUpdate,118} from './ReactFiberBeginWork';119import {120  getIsHydrating,121  tryToClaimNextHydratableFormMarkerInstance,122} from './ReactFiberHydrationContext';123import {124  markStateUpdateScheduled,125  setIsStrictModeForDevtools,126} from './ReactFiberDevToolsHook';127import {128  startUpdateTimerByLane,129  startHostActionTimer,130} from './ReactProfilerTimer';131import {createCache} from './ReactFiberCacheComponent';132import {133  createUpdate as createLegacyQueueUpdate,134  enqueueUpdate as enqueueLegacyQueueUpdate,135  entangleTransitions as entangleLegacyQueueTransitions,136} from './ReactFiberClassUpdateQueue';137import {138  enqueueConcurrentHookUpdate,139  enqueueConcurrentHookUpdateAndEagerlyBailout,140  enqueueConcurrentRenderForLane,141} from './ReactFiberConcurrentUpdates';142import {getTreeId} from './ReactFiberTreeContext';143import {now} from './Scheduler';144import {145  trackUsedThenable,146  checkIfUseWrappedInTryCatch,147  checkIfUseWasUsedBefore,148  createThenableState,149  SuspenseException,150  SuspenseActionException,151} from './ReactFiberThenable';152import type {ThenableState} from './ReactFiberThenable';153import type {Transition} from 'react/src/ReactStartTransition';154import {155  peekEntangledActionLane,156  peekEntangledActionThenable,157  chainThenableValue,158} from './ReactFiberAsyncAction';159import {requestTransitionLane} from './ReactFiberRootScheduler';160import {isCurrentTreeHidden} from './ReactFiberHiddenContext';161import {requestCurrentTransition} from './ReactFiberTransition';162163import {callComponentInDEV} from './ReactFiberCallUserSpace';164165import {scheduleGesture} from './ReactFiberGestureScheduler';166167export type Update<S, A> = {168  lane: Lane,169  revertLane: Lane,170  action: A,171  hasEagerState: boolean,172  eagerState: S | null,173  next: Update<S, A>,174  gesture: null | ScheduledGesture, // enableGestureTransition175};176177export type UpdateQueue<S, A> = {178  pending: Update<S, A> | null,179  lanes: Lanes,180  dispatch: (A => mixed) | null,181  lastRenderedReducer: ((S, A) => S) | null,182  lastRenderedState: S | null,183};184185let didWarnAboutMismatchedHooksForComponent;186let didWarnUncachedGetSnapshot: void | true;187let didWarnAboutUseWrappedInTryCatch;188let didWarnAboutAsyncClientComponent;189let didWarnAboutUseFormState;190if (__DEV__) {191  didWarnAboutMismatchedHooksForComponent = new Set<string | null>();192  didWarnAboutUseWrappedInTryCatch = new Set<string | null>();193  didWarnAboutAsyncClientComponent = new Set<string | null>();194  didWarnAboutUseFormState = new Set<string | null>();195}196197export type Hook = {198  memoizedState: any,199  baseState: any,200  baseQueue: Update<any, any> | null,201  queue: any,202  next: Hook | null,203};204205// The effect "instance" is a shared object that remains the same for the entire206// lifetime of an effect. In Rust terms, a RefCell. We use it to store the207// "destroy" function that is returned from an effect, because that is stateful.208// The field is `undefined` if the effect is unmounted, or if the effect ran209// but is not stateful. We don't explicitly track whether the effect is mounted210// or unmounted because that can be inferred by the hiddenness of the fiber in211// the tree, i.e. whether there is a hidden Offscreen fiber above it.212//213// It's unfortunate that this is stored on a separate object, because it adds214// more memory per effect instance, but it's conceptually sound. I think there's215// likely a better data structure we could use for effects; perhaps just one216// array of effect instances per fiber. But I think this is OK for now despite217// the additional memory and we can follow up with performance218// optimizations later.219type EffectInstance = {220  destroy: void | (() => void),221};222223export type Effect = {224  tag: HookFlags,225  inst: EffectInstance,226  create: () => (() => void) | void,227  deps: Array<mixed> | void | null,228  next: Effect,229};230231type StoreInstance<T> = {232  value: T,233  getSnapshot: () => T,234};235236type StoreConsistencyCheck<T> = {237  value: T,238  getSnapshot: () => T,239};240241type EventFunctionPayload<Args, Return, F: (...Array<Args>) => Return> = {242  ref: {243    eventFn: F,244    impl: F,245  },246  nextImpl: F,247};248249export type FunctionComponentUpdateQueue = {250  lastEffect: Effect | null,251  events: Array<EventFunctionPayload<any, any, any>> | null,252  stores: Array<StoreConsistencyCheck<any>> | null,253  memoCache: MemoCache | null,254};255256type BasicStateAction<S> = (S => S) | S;257258type Dispatch<A> = A => void;259260// These are set right before calling the component.261let renderLanes: Lanes = NoLanes;262// The work-in-progress fiber. I've named it differently to distinguish it from263// the work-in-progress hook.264let currentlyRenderingFiber: Fiber = null as any;265266// Hooks are stored as a linked list on the fiber's memoizedState field. The267// current hook list is the list that belongs to the current fiber. The268// work-in-progress hook list is a new list that will be added to the269// work-in-progress fiber.270let currentHook: Hook | null = null;271let workInProgressHook: Hook | null = null;272273// Whether an update was scheduled at any point during the render phase. This274// does not get reset if we do another render pass; only when we're completely275// finished evaluating this component. This is an optimization so we know276// whether we need to clear render phase updates after a throw.277let didScheduleRenderPhaseUpdate: boolean = false;278// Where an update was scheduled only during the current render pass. This279// gets reset after each attempt.280// TODO: Maybe there's some way to consolidate this with281// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.282let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;283let shouldDoubleInvokeUserFnsInHooksDEV: boolean = false;284// Counts the number of useId hooks in this component.285let localIdCounter: number = 0;286// Counts number of `use`-d thenables287let thenableIndexCounter: number = 0;288let thenableState: ThenableState | null = null;289290// Used for ids that are generated completely client-side (i.e. not during291// hydration). This counter is global, so client ids are not stable across292// render attempts.293let globalClientIdCounter: number = 0;294295const RE_RENDER_LIMIT = 25;296297// In DEV, this is the name of the currently executing primitive hook298let currentHookNameInDev: ?HookType = null;299300// In DEV, this list ensures that hooks are called in the same order between renders.301// The list stores the order of hooks used during the initial render (mount).302// Subsequent renders (updates) reference this list.303let hookTypesDev: Array<HookType> | null = null;304let hookTypesUpdateIndexDev: number = -1;305306// In DEV, this tracks whether currently rendering component needs to ignore307// the dependencies for Hooks that need them (e.g. useEffect or useMemo).308// When true, such Hooks will always be "remounted". Only used during hot reload.309let ignorePreviousDependencies: boolean = false;310311function mountHookTypesDev(): void {312  if (__DEV__) {313    const hookName = currentHookNameInDev as any as HookType;314315    if (hookTypesDev === null) {316      hookTypesDev = [hookName];317    } else {318      hookTypesDev.push(hookName);319    }320  }321}322323function updateHookTypesDev(): void {324  if (__DEV__) {325    const hookName = currentHookNameInDev as any as HookType;326327    if (hookTypesDev !== null) {328      hookTypesUpdateIndexDev++;329      if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {330        warnOnHookMismatchInDev(hookName);331      }332    }333  }334}335336function checkDepsAreArrayDev(deps: mixed): void {337  if (__DEV__) {338    if (deps !== undefined && deps !== null && !isArray(deps)) {339      // Verify deps, but only on mount to avoid extra checks.340      // It's unlikely their type would change as usually you define them inline.341      console.error(342        '%s received a final argument that is not an array (instead, received `%s`). When ' +343          'specified, the final argument must be an array.',344        currentHookNameInDev,345        typeof deps,346      );347    }348  }349}350351function warnOnHookMismatchInDev(currentHookName: HookType): void {352  if (__DEV__) {353    const componentName = getComponentNameFromFiber(currentlyRenderingFiber);354    if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {355      didWarnAboutMismatchedHooksForComponent.add(componentName);356357      if (hookTypesDev !== null) {358        let table = '';359360        const secondColumnStart = 30;361362        for (let i = 0; i <= (hookTypesUpdateIndexDev as any as number); i++) {363          const oldHookName = hookTypesDev[i];364          const newHookName =365            i === (hookTypesUpdateIndexDev as any as number)366              ? currentHookName367              : oldHookName;368369          let row = `${i + 1}. ${oldHookName}`;370371          // Extra space so second column lines up372          // lol @ IE not supporting String#repeat373          while (row.length < secondColumnStart) {374            row += ' ';375          }376377          row += newHookName + '\n';378379          table += row;380        }381382        console.error(383          'React has detected a change in the order of Hooks called by %s. ' +384            'This will lead to bugs and errors if not fixed. ' +385            'For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +386            '   Previous render            Next render\n' +387            '   ------------------------------------------------------\n' +388            '%s' +389            '   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',390          componentName,391          table,392        );393      }394    }395  }396}397398function warnOnUseFormStateInDev(): void {399  if (__DEV__) {400    const componentName = getComponentNameFromFiber(currentlyRenderingFiber);401    if (!didWarnAboutUseFormState.has(componentName)) {402      didWarnAboutUseFormState.add(componentName);403404      console.error(405        'ReactDOM.useFormState has been renamed to React.useActionState. ' +406          'Please update %s to use React.useActionState.',407        componentName,408      );409    }410  }411}412413function warnIfAsyncClientComponent(Component: Function) {414  if (__DEV__) {415    // This dev-only check only works for detecting native async functions,416    // not transpiled ones. There's also a prod check that we use to prevent417    // async client components from crashing the app; the prod one works even418    // for transpiled async functions. Neither mechanism is completely419    // bulletproof but together they cover the most common cases.420    const isAsyncFunction =421      // $FlowFixMe[method-unbinding]422      Object.prototype.toString.call(Component) === '[object AsyncFunction]' ||423      // $FlowFixMe[method-unbinding]424      Object.prototype.toString.call(Component) ===425        '[object AsyncGeneratorFunction]';426    if (isAsyncFunction) {427      // Encountered an async Client Component. This is not yet supported.428      const componentName = getComponentNameFromFiber(currentlyRenderingFiber);429      if (!didWarnAboutAsyncClientComponent.has(componentName)) {430        didWarnAboutAsyncClientComponent.add(componentName);431        console.error(432          '%s is an async Client Component. ' +433            'Only Server Components can be async at the moment. This error is often caused by accidentally ' +434            "adding `'use client'` to a module that was originally written " +435            'for the server.',436          componentName === null437            ? 'An unknown Component'438            : `<${componentName}>`,439        );440      }441    }442  }443}444445function throwInvalidHookError() {446  throw new Error(447    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +448      ' one of the following reasons:\n' +449      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +450      '2. You might be breaking the Rules of Hooks\n' +451      '3. You might have more than one copy of React in the same app\n' +452      'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',453  );454}455456function areHookInputsEqual(457  nextDeps: Array<mixed>,458  prevDeps: Array<mixed> | null,459): boolean {460  if (__DEV__) {461    if (ignorePreviousDependencies) {462      // Only true when this component is being hot reloaded.463      return false;464    }465  }466467  if (prevDeps === null) {468    if (__DEV__) {469      console.error(470        '%s received a final argument during this render, but not during ' +471          'the previous render. Even though the final argument is optional, ' +472          'its type cannot change between renders.',473        currentHookNameInDev,474      );475    }476    return false;477  }478479  if (__DEV__) {480    // Don't bother comparing lengths in prod because these arrays should be481    // passed inline.482    if (nextDeps.length !== prevDeps.length) {483      console.error(484        'The final argument passed to %s changed size between renders. The ' +485          'order and size of this array must remain constant.\n\n' +486          'Previous: %s\n' +487          'Incoming: %s',488        currentHookNameInDev,489        `[${prevDeps.join(', ')}]`,490        `[${nextDeps.join(', ')}]`,491      );492    }493  }494  // $FlowFixMe[incompatible-use] found when upgrading Flow495  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {496    // $FlowFixMe[incompatible-use] found when upgrading Flow497    if (is(nextDeps[i], prevDeps[i])) {498      continue;499    }500    return false;501  }502  return true;503}504505export function renderWithHooks<Props, SecondArg>(506  current: Fiber | null,507  workInProgress: Fiber,508  Component: (p: Props, arg: SecondArg) => any,509  props: Props,510  secondArg: SecondArg,511  nextRenderLanes: Lanes,512): any {513  renderLanes = nextRenderLanes;514  currentlyRenderingFiber = workInProgress;515516  if (__DEV__) {517    hookTypesDev =518      current !== null519        ? (current._debugHookTypes as any as Array<HookType>)520        : null;521    hookTypesUpdateIndexDev = -1;522    // Used for hot reloading:523    ignorePreviousDependencies =524      current !== null && current.type !== workInProgress.type;525526    warnIfAsyncClientComponent(Component);527  }528529  workInProgress.memoizedState = null;530  workInProgress.updateQueue = null;531  workInProgress.lanes = NoLanes;532533  // The following should have already been reset534  // currentHook = null;535  // workInProgressHook = null;536537  // didScheduleRenderPhaseUpdate = false;538  // localIdCounter = 0;539  // thenableIndexCounter = 0;540  // thenableState = null;541542  // TODO Warn if no hooks are used at all during mount, then some are used during update.543  // Currently we will identify the update render as a mount because memoizedState === null.544  // This is tricky because it's valid for certain types of components (e.g. React.lazy)545546  // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.547  // Non-stateful hooks (e.g. context) don't get added to memoizedState,548  // so memoizedState would be null during updates and mounts.549  if (__DEV__) {550    if (current !== null && current.memoizedState !== null) {551      ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;552    } else if (hookTypesDev !== null) {553      // This dispatcher handles an edge case where a component is updating,554      // but no stateful hooks have been used.555      // We want to match the production code behavior (which will use HooksDispatcherOnMount),556      // but with the extra DEV validation to ensure hooks ordering hasn't changed.557      // This dispatcher does that.558      ReactSharedInternals.H = HooksDispatcherOnMountWithHookTypesInDEV;559    } else {560      ReactSharedInternals.H = HooksDispatcherOnMountInDEV;561    }562  } else {563    ReactSharedInternals.H =564      current === null || current.memoizedState === null565        ? HooksDispatcherOnMount566        : HooksDispatcherOnUpdate;567  }568569  // In Strict Mode, during development, user functions are double invoked to570  // help detect side effects. The logic for how this is implemented for in571  // hook components is a bit complex so let's break it down.572  //573  // We will invoke the entire component function twice. However, during the574  // second invocation of the component, the hook state from the first575  // invocation will be reused. That means things like `useMemo` functions won't576  // run again, because the deps will match and the memoized result will577  // be reused.578  //579  // We want memoized functions to run twice, too, so account for this, user580  // functions are double invoked during the *first* invocation of the component581  // function, and are *not* double invoked during the second incovation:582  //583  // - First execution of component function: user functions are double invoked584  // - Second execution of component function (in Strict Mode, during585  //   development): user functions are not double invoked.586  //587  // This is intentional for a few reasons; most importantly, it's because of588  // how `use` works when something suspends: it reuses the promise that was589  // passed during the first attempt. This is itself a form of memoization.590  // We need to be able to memoize the reactive inputs to the `use` call using591  // a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must592  // come from the same component invocation as the output.593  //594  // There are plenty of tests to ensure this behavior is correct.595  const shouldDoubleRenderDEV =596    __DEV__ && (workInProgress.mode & StrictLegacyMode) !== NoMode;597598  shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;599  let children = __DEV__600    ? callComponentInDEV(Component, props, secondArg)601    : Component(props, secondArg);602  shouldDoubleInvokeUserFnsInHooksDEV = false;603604  // Check if there was a render phase update605  if (didScheduleRenderPhaseUpdateDuringThisPass) {606    // Keep rendering until the component stabilizes (there are no more render607    // phase updates).608    children = renderWithHooksAgain(609      workInProgress,610      Component,611      props,612      secondArg,613    );614  }615616  if (shouldDoubleRenderDEV) {617    // In development, components are invoked twice to help detect side effects.618    setIsStrictModeForDevtools(true);619    try {620      children = renderWithHooksAgain(621        workInProgress,622        Component,623        props,624        secondArg,625      );626    } finally {627      setIsStrictModeForDevtools(false);628    }629  }630631  finishRenderingHooks(current, workInProgress, Component);632633  return children;634}635636function finishRenderingHooks<Props, SecondArg>(637  current: Fiber | null,638  workInProgress: Fiber,639  Component: (p: Props, arg: SecondArg) => any,640): void {641  if (__DEV__) {642    workInProgress._debugHookTypes = hookTypesDev;643    // Stash the thenable state for use by DevTools.644    if (workInProgress.dependencies === null) {645      if (thenableState !== null) {646        workInProgress.dependencies = {647          lanes: NoLanes,648          firstContext: null,649          _debugThenableState: thenableState,650        };651      }652    } else {653      workInProgress.dependencies._debugThenableState = thenableState;654    }655    checkIfUseWasUsedBefore(workInProgress, thenableState);656  }657658  // We can assume the previous dispatcher is always this one, since we set it659  // at the beginning of the render phase and there's no re-entrance.660  ReactSharedInternals.H = ContextOnlyDispatcher;661662  // This check uses currentHook so that it works the same in DEV and prod bundles.663  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.664  const didRenderTooFewHooks =665    currentHook !== null && currentHook.next !== null;666667  renderLanes = NoLanes;668  currentlyRenderingFiber = null as any;669670  currentHook = null;671  workInProgressHook = null;672673  if (__DEV__) {674    currentHookNameInDev = null;675    hookTypesDev = null;676    hookTypesUpdateIndexDev = -1;677678    // Confirm that a static flag was not added or removed since the last679    // render. If this fires, it suggests that we incorrectly reset the static680    // flags in some other part of the codebase. This has happened before, for681    // example, in the SuspenseList implementation.682    if (683      current !== null &&684      (current.flags & StaticMaskEffect) !==685        (workInProgress.flags & StaticMaskEffect) &&686      // Disable this warning in legacy mode, because legacy Suspense is weird687      // and creates false positives. To make this work in legacy mode, we'd688      // need to mark fibers that commit in an incomplete state, somehow. For689      // now I'll disable the warning that most of the bugs that would trigger690      // it are either exclusive to concurrent mode or exist in both.691      (disableLegacyMode || (current.mode & ConcurrentMode) !== NoMode)692    ) {693      console.error(694        'Internal React error: Expected static flag was missing. Please ' +695          'notify the React team.',696      );697    }698  }699700  didScheduleRenderPhaseUpdate = false;701  // This is reset by checkDidRenderIdHook702  // localIdCounter = 0;703704  thenableIndexCounter = 0;705  thenableState = null;706707  if (didRenderTooFewHooks) {708    throw new Error(709      'Rendered fewer hooks than expected. This may be caused by an accidental ' +710        'early return statement.',711    );712  }713714  if (current !== null) {715    if (!checkIfWorkInProgressReceivedUpdate()) {716      // If there were no changes to props or state, we need to check if there717      // was a context change. We didn't already do this because there's no718      // 1:1 correspondence between dependencies and hooks. Although, because719      // there almost always is in the common case (`readContext` is an720      // internal API), we could compare in there. OTOH, we only hit this case721      // if everything else bails out, so on the whole it might be better to722      // keep the comparison out of the common path.723      const currentDependencies = current.dependencies;724      if (725        currentDependencies !== null &&726        checkIfContextChanged(currentDependencies)727      ) {728        markWorkInProgressReceivedUpdate();729      }730    }731  }732733  if (__DEV__) {734    if (checkIfUseWrappedInTryCatch()) {735      const componentName =736        getComponentNameFromFiber(workInProgress) || 'Unknown';737      if (738        !didWarnAboutUseWrappedInTryCatch.has(componentName) &&739        // This warning also fires if you suspend with `use` inside an740        // async component. Since we warn for that above, we'll silence this741        // second warning by checking here.742        !didWarnAboutAsyncClientComponent.has(componentName)743      ) {744        didWarnAboutUseWrappedInTryCatch.add(componentName);745        console.error(746          '`use` was called from inside a try/catch block. This is not allowed ' +747            'and can lead to unexpected behavior. To handle errors triggered ' +748            'by `use`, wrap your component in a error boundary.',749        );750      }751    }752  }753}754755export function replaySuspendedComponentWithHooks<Props, SecondArg>(756  current: Fiber | null,757  workInProgress: Fiber,758  Component: (p: Props, arg: SecondArg) => any,759  props: Props,760  secondArg: SecondArg,761): any {762  // This function is used to replay a component that previously suspended,763  // after its data resolves.764  //765  // It's a simplified version of renderWithHooks, but it doesn't need to do766  // most of the set up work because they weren't reset when we suspended; they767  // only get reset when the component either completes (finishRenderingHooks)768  // or unwinds (resetHooksOnUnwind).769  if (__DEV__) {770    hookTypesUpdateIndexDev = -1;771    // Used for hot reloading:772    ignorePreviousDependencies =773      current !== null && current.type !== workInProgress.type;774  }775  // renderWithHooks only resets the updateQueue but does not clear it, since776  // it needs to work for both this case (suspense replay) as well as for double777  // renders in dev and setState-in-render. However, for the suspense replay case778  // we need to reset the updateQueue to correctly handle unmount effects, so we779  // clear the queue here780  workInProgress.updateQueue = null;781  const children = renderWithHooksAgain(782    workInProgress,783    Component,784    props,785    secondArg,786  );787  finishRenderingHooks(current, workInProgress, Component);788  return children;789}790791function renderWithHooksAgain<Props, SecondArg>(792  workInProgress: Fiber,793  Component: (p: Props, arg: SecondArg) => any,794  props: Props,795  secondArg: SecondArg,796): any {797  // This is used to perform another render pass. It's used when setState is798  // called during render, and for double invoking components in Strict Mode799  // during development.800  //801  // The state from the previous pass is reused whenever possible. So, state802  // updates that were already processed are not processed again, and memoized803  // functions (`useMemo`) are not invoked again.804  //805  // Keep rendering in a loop for as long as render phase updates continue to806  // be scheduled. Use a counter to prevent infinite loops.807808  currentlyRenderingFiber = workInProgress;809810  let numberOfReRenders: number = 0;811  let children;812  do {813    if (didScheduleRenderPhaseUpdateDuringThisPass) {814      // It's possible that a use() value depended on a state that was updated in815      // this rerender, so we need to watch for different thenables this time.816      thenableState = null;817    }818    thenableIndexCounter = 0;819    didScheduleRenderPhaseUpdateDuringThisPass = false;820821    if (numberOfReRenders >= RE_RENDER_LIMIT) {822      throw new Error(823        'Too many re-renders. React limits the number of renders to prevent ' +824          'an infinite loop.',825      );826    }827828    numberOfReRenders += 1;829    if (__DEV__) {830      // Even when hot reloading, allow dependencies to stabilize831      // after first render to prevent infinite render phase updates.832      ignorePreviousDependencies = false;833    }834835    // Start over from the beginning of the list836    currentHook = null;837    workInProgressHook = null;838839    if (workInProgress.updateQueue != null) {840      resetFunctionComponentUpdateQueue(workInProgress.updateQueue as any);841    }842843    if (__DEV__) {844      // Also validate hook order for cascading updates.845      hookTypesUpdateIndexDev = -1;846    }847848    ReactSharedInternals.H = __DEV__849      ? HooksDispatcherOnRerenderInDEV850      : HooksDispatcherOnRerender;851852    children = __DEV__853      ? callComponentInDEV(Component, props, secondArg)854      : Component(props, secondArg);855  } while (didScheduleRenderPhaseUpdateDuringThisPass);856  return children;857}858859export function renderTransitionAwareHostComponentWithHooks(860  current: Fiber | null,861  workInProgress: Fiber,862  lanes: Lanes,863): TransitionStatus {864  return renderWithHooks(865    current,866    workInProgress,867    TransitionAwareHostComponent,868    null,869    null,870    lanes,871  );872}873874export function TransitionAwareHostComponent(): TransitionStatus {875  const dispatcher: any = ReactSharedInternals.H;876  const [maybeThenable] = dispatcher.useState();877  let nextState;878  if (typeof maybeThenable.then === 'function') {879    const thenable: Thenable<TransitionStatus> = maybeThenable as any;880    nextState = useThenable(thenable);881  } else {882    const status: TransitionStatus = maybeThenable;883    nextState = status;884  }885886  // The "reset state" is an object. If it changes, that means something887  // requested that we reset the form.888  const [nextResetState] = dispatcher.useState();889  const prevResetState =890    currentHook !== null ? currentHook.memoizedState : null;891  if (prevResetState !== nextResetState) {892    // Schedule a form reset893    currentlyRenderingFiber.flags |= FormReset;894  }895896  return nextState;897}898899export function checkDidRenderIdHook(): boolean {900  // This should be called immediately after every renderWithHooks call.901  // Conceptually, it's part of the return value of renderWithHooks; it's only a902  // separate function to avoid using an array tuple.903  const didRenderIdHook = localIdCounter !== 0;904  localIdCounter = 0;905  return didRenderIdHook;906}907908export function bailoutHooks(909  current: Fiber,910  workInProgress: Fiber,911  lanes: Lanes,912): void {913  workInProgress.updateQueue = current.updateQueue;914  // TODO: Don't need to reset the flags here, because they're reset in the915  // complete phase (bubbleProperties).916  if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {917    workInProgress.flags &= ~(918      MountPassiveDevEffect |919      MountLayoutDevEffect |920      PassiveEffect |921      UpdateEffect922    );923  } else {924    workInProgress.flags &= ~(PassiveEffect | UpdateEffect);925  }926  current.lanes = removeLanes(current.lanes, lanes);927}928929export function resetHooksAfterThrow(): void {930  // This is called immediaetly after a throw. It shouldn't reset the entire931  // module state, because the work loop might decide to replay the component932  // again without rewinding.933  //934  // It should only reset things like the current dispatcher, to prevent hooks935  // from being called outside of a component.936  currentlyRenderingFiber = null as any;937938  // We can assume the previous dispatcher is always this one, since we set it939  // at the beginning of the render phase and there's no re-entrance.940  ReactSharedInternals.H = ContextOnlyDispatcher;941}942943export function resetHooksOnUnwind(workInProgress: Fiber): void {944  if (didScheduleRenderPhaseUpdate) {945    // There were render phase updates. These are only valid for this render946    // phase, which we are now aborting. Remove the updates from the queues so947    // they do not persist to the next render. Do not remove updates from hooks948    // that weren't processed.949    //950    // Only reset the updates from the queue if it has a clone. If it does951    // not have a clone, that means it wasn't processed, and the updates were952    // scheduled before we entered the render phase.953    let hook: Hook | null = workInProgress.memoizedState;954    while (hook !== null) {955      const queue = hook.queue;956      if (queue !== null) {957        queue.pending = null;958      }959      hook = hook.next;960    }961    didScheduleRenderPhaseUpdate = false;962  }963964  renderLanes = NoLanes;965  currentlyRenderingFiber = null as any;966967  currentHook = null;968  workInProgressHook = null;969970  if (__DEV__) {971    hookTypesDev = null;972    hookTypesUpdateIndexDev = -1;973974    currentHookNameInDev = null;975  }976977  didScheduleRenderPhaseUpdateDuringThisPass = false;978  localIdCounter = 0;979  thenableIndexCounter = 0;980  thenableState = null;981}982983function mountWorkInProgressHook(): Hook {984  const hook: Hook = {985    memoizedState: null,986987    baseState: null,988    baseQueue: null,989    queue: null,990991    next: null,992  };993994  if (workInProgressHook === null) {995    // This is the first hook in the list996    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;997  } else {998    // Append to the end of the list999    workInProgressHook = workInProgressHook.next = hook;1000  }1001  return workInProgressHook;1002}10031004function updateWorkInProgressHook(): Hook {1005  // This function is used both for updates and for re-renders triggered by a1006  // render phase update. It assumes there is either a current hook we can1007  // clone, or a work-in-progress hook from a previous render pass that we can1008  // use as a base.1009  let nextCurrentHook: null | Hook;1010  if (currentHook === null) {1011    const current = currentlyRenderingFiber.alternate;1012    if (current !== null) {1013      nextCurrentHook = current.memoizedState;1014    } else {1015      nextCurrentHook = null;1016    }1017  } else {1018    nextCurrentHook = currentHook.next;1019  }10201021  let nextWorkInProgressHook: null | Hook;1022  if (workInProgressHook === null) {1023    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;1024  } else {1025    nextWorkInProgressHook = workInProgressHook.next;1026  }10271028  if (nextWorkInProgressHook !== null) {1029    // There's already a work-in-progress. Reuse it.1030    workInProgressHook = nextWorkInProgressHook;1031    nextWorkInProgressHook = workInProgressHook.next;10321033    currentHook = nextCurrentHook;1034  } else {1035    // Clone from the current hook.10361037    if (nextCurrentHook === null) {1038      const currentFiber = currentlyRenderingFiber.alternate;1039      if (currentFiber === null) {1040        // This is the initial render. This branch is reached when the component1041        // suspends, resumes, then renders an additional hook.1042        // Should never be reached because we should switch to the mount dispatcher first.1043        throw new Error(1044          'Update hook called on initial render. This is likely a bug in React. Please file an issue.',1045        );1046      } else {1047        // This is an update. We should always have a current hook.1048        throw new Error('Rendered more hooks than during the previous render.');1049      }1050    }10511052    currentHook = nextCurrentHook;10531054    const newHook: Hook = {1055      memoizedState: currentHook.memoizedState,10561057      baseState: currentHook.baseState,1058      baseQueue: currentHook.baseQueue,1059      queue: currentHook.queue,10601061      next: null,1062    };10631064    if (workInProgressHook === null) {1065      // This is the first hook in the list.1066      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;1067    } else {1068      // Append to the end of the list.1069      workInProgressHook = workInProgressHook.next = newHook;1070    }1071  }1072  return workInProgressHook;1073}10741075function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {1076  return {1077    lastEffect: null,1078    events: null,1079    stores: null,1080    memoCache: null,1081  };1082}10831084function resetFunctionComponentUpdateQueue(1085  updateQueue: FunctionComponentUpdateQueue,1086): void {1087  updateQueue.lastEffect = null;1088  updateQueue.events = null;1089  updateQueue.stores = null;1090  if (updateQueue.memoCache != null) {1091    // NOTE: this function intentionally does not reset memoCache data. We reuse updateQueue for the memo1092    // cache to avoid increasing the size of fibers that don't need a cache, but we don't want to reset1093    // the cache when other properties are reset.1094    updateQueue.memoCache.index = 0;1095  }1096}10971098function useThenable<T>(thenable: Thenable<T>): T {1099  // Track the position of the thenable within this fiber.1100  const index = thenableIndexCounter;1101  thenableIndexCounter += 1;1102  if (thenableState === null) {1103    thenableState = createThenableState();1104  }1105  const result = trackUsedThenable(1106    thenableState,1107    thenable,1108    index,1109    __DEV__ ? currentlyRenderingFiber : null,1110  );11111112  // When something suspends with `use`, we replay the component with the1113  // "re-render" dispatcher instead of the "mount" or "update" dispatcher.1114  //1115  // But if there are additional hooks that occur after the `use` invocation1116  // that suspended, they wouldn't have been processed during the previous1117  // attempt. So after we invoke `use` again, we may need to switch from the1118  // "re-render" dispatcher back to the "mount" or "update" dispatcher. That's1119  // what the following logic accounts for.1120  //1121  // TODO: Theoretically this logic only needs to go into the rerender1122  // dispatcher. Could optimize, but probably not be worth it.11231124  // This is the same logic as in updateWorkInProgressHook.1125  const workInProgressFiber = currentlyRenderingFiber;1126  const nextWorkInProgressHook =1127    workInProgressHook === null1128      ? // We're at the beginning of the list, so read from the first hook from1129        // the fiber.1130        workInProgressFiber.memoizedState1131      : workInProgressHook.next;11321133  if (nextWorkInProgressHook !== null) {1134    // There are still hooks remaining from the previous attempt.1135  } else {1136    // There are no remaining hooks from the previous attempt. We're no longer1137    // in "re-render" mode. Switch to the normal mount or update dispatcher.1138    //1139    // This is the same as the logic in renderWithHooks, except we don't bother1140    // to track the hook types debug information in this case (sufficient to1141    // only do that when nothing suspends).1142    const currentFiber = workInProgressFiber.alternate;1143    if (__DEV__) {1144      if (currentFiber !== null && currentFiber.memoizedState !== null) {1145        ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;1146      } else {1147        ReactSharedInternals.H = HooksDispatcherOnMountInDEV;1148      }1149    } else {1150      ReactSharedInternals.H =1151        currentFiber === null || currentFiber.memoizedState === null1152          ? HooksDispatcherOnMount1153          : HooksDispatcherOnUpdate;1154    }1155  }1156  return result;1157}11581159function use<T>(usable: Usable<T>): T {1160  // $FlowFixMe[invalid-compare]1161  if (usable !== null && typeof usable === 'object') {1162    // $FlowFixMe[method-unbinding]1163    if (typeof usable.then === 'function') {1164      // This is a thenable.1165      const thenable: Thenable<T> = usable as any;1166      return useThenable(thenable);1167    } else if (usable.$$typeof === REACT_RECOVERABLE_TYPE) {1168      // Fiber is the final renderer, so there is no downstream host that1169      // needs to recover this subtree. Continue rendering through it.1170      return undefined as any;1171    } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {1172      const context: ReactContext<T> = usable as any;1173      return readContext(context);1174    }1175  }11761177  // eslint-disable-next-line react-internal/safe-string-coercion1178  throw new Error('An unsupported type was passed to use(): ' + String(usable));1179}11801181function useMemoCache(size: number): Array<mixed> {1182  let memoCache = null;1183  // Fast-path, load memo cache from wip fiber if already prepared1184  let updateQueue: FunctionComponentUpdateQueue | null =1185    currentlyRenderingFiber.updateQueue as any;1186  if (updateQueue !== null) {1187    memoCache = updateQueue.memoCache;1188  }1189  // Otherwise clone from the current fiber1190  if (memoCache == null) {1191    const current: Fiber | null = currentlyRenderingFiber.alternate;1192    if (current !== null) {1193      const currentUpdateQueue: FunctionComponentUpdateQueue | null =1194        current.updateQueue as any;1195      if (currentUpdateQueue !== null) {1196        const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;1197        if (currentMemoCache != null) {1198          memoCache = {1199            // When enableNoCloningMemoCache is enabled, instead of treating the1200            // cache as copy-on-write, like we do with fibers, we share the same1201            // cache instance across all render attempts, even if the component1202            // is interrupted before it commits.1203            //1204            // If an update is interrupted, either because it suspended or1205            // because of another update, we can reuse the memoized computations1206            // from the previous attempt. We can do this because the React1207            // Compiler performs atomic writes to the memo cache, i.e. it will1208            // not record the inputs to a memoization without also recording its1209            // output.1210            //1211            // This gives us a form of "resuming" within components and hooks.1212            //1213            // This only works when updating a component that already mounted.1214            // It has no impact during initial render, because the memo cache is1215            // stored on the fiber, and since we have not implemented resuming1216            // for fibers, it's always a fresh memo cache, anyway.1217            //1218            // However, this alone is pretty useful — it happens whenever you1219            // update the UI with fresh data after a mutation/action, which is1220            // extremely common in a Suspense-driven (e.g. RSC or Relay) app.1221            data: enableNoCloningMemoCache1222              ? currentMemoCache.data1223              : // Clone the memo cache before each render (copy-on-write)1224                currentMemoCache.data.map(array => array.slice()),1225            index: 0 as number,1226          };1227        }1228      }1229    }1230  }1231  // Finally fall back to allocating a fresh instance of the cache1232  if (memoCache == null) {1233    memoCache = {1234      data: [],1235      index: 0 as number,1236    };1237  }1238  if (updateQueue === null) {1239    updateQueue = createFunctionComponentUpdateQueue();1240    currentlyRenderingFiber.updateQueue = updateQueue;1241  }1242  updateQueue.memoCache = memoCache;12431244  let data = memoCache.data[memoCache.index];1245  if (data === undefined || (__DEV__ && ignorePreviousDependencies)) {1246    data = memoCache.data[memoCache.index] = new Array(size);1247    for (let i = 0; i < size; i++) {1248      data[i] = REACT_MEMO_CACHE_SENTINEL;1249    }1250  } else if (data.length !== size) {1251    // TODO: consider warning or throwing here1252    if (__DEV__) {1253      console.error(1254        'Expected a constant size argument for each invocation of useMemoCache. ' +1255          'The previous cache was allocated with size %s but size %s was requested.',1256        data.length,1257        size,1258      );1259    }1260  }1261  memoCache.index++;1262  return data;1263}12641265function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {1266  // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1267  return typeof action === 'function' ? action(state) : action;1268}12691270function mountReducer<S, I, A>(1271  reducer: (S, A) => S,1272  initialArg: I,1273  init?: I => S,1274): [S, Dispatch<A>] {1275  const hook = mountWorkInProgressHook();1276  let initialState;1277  if (init !== undefined) {1278    initialState = init(initialArg);1279    if (shouldDoubleInvokeUserFnsInHooksDEV) {1280      setIsStrictModeForDevtools(true);1281      try {1282        init(initialArg);1283      } finally {1284        setIsStrictModeForDevtools(false);1285      }1286    }1287  } else {1288    initialState = initialArg as any as S;1289  }1290  hook.memoizedState = hook.baseState = initialState;1291  const queue: UpdateQueue<S, A> = {1292    pending: null,1293    lanes: NoLanes,1294    dispatch: null,1295    lastRenderedReducer: reducer,1296    lastRenderedState: initialState as any,1297  };1298  hook.queue = queue;1299  const dispatch: Dispatch<A> = (queue.dispatch = dispatchReducerAction.bind(1300    null,1301    currentlyRenderingFiber,1302    queue,1303  ) as any);1304  return [hook.memoizedState, dispatch];1305}13061307function updateReducer<S, I, A>(1308  reducer: (S, A) => S,1309  initialArg: I,1310  init?: I => S,1311): [S, Dispatch<A>] {1312  const hook = updateWorkInProgressHook();1313  return updateReducerImpl(hook, currentHook as any as Hook, reducer);1314}13151316function updateReducerImpl<S, A>(1317  hook: Hook,1318  current: Hook,1319  reducer: (S, A) => S,1320): [S, Dispatch<A>] {1321  const queue = hook.queue;13221323  if (queue === null) {1324    throw new Error(1325      'Should have a queue. You are likely calling Hooks conditionally, ' +1326        'which is not allowed. (https://react.dev/link/invalid-hook-call)',1327    );1328  }13291330  queue.lastRenderedReducer = reducer;13311332  // The last rebase update that is NOT part of the base state.1333  let baseQueue = hook.baseQueue;13341335  // The last pending update that hasn't been processed yet.1336  const pendingQueue = queue.pending;1337  if (pendingQueue !== null) {1338    // We have new updates that haven't been processed yet.1339    // We'll add them to the base queue.1340    if (baseQueue !== null) {1341      // Merge the pending queue and the base queue.1342      const baseFirst = baseQueue.next;1343      const pendingFirst = pendingQueue.next;1344      baseQueue.next = pendingFirst;1345      pendingQueue.next = baseFirst;1346    }1347    if (__DEV__) {1348      if (current.baseQueue !== baseQueue) {1349        // Internal invariant that should never happen, but feasibly could in1350        // the future if we implement resuming, or some form of that.1351        console.error(1352          'Internal error: Expected work-in-progress queue to be a clone. ' +1353            'This is a bug in React.',1354        );1355      }1356    }1357    current.baseQueue = baseQueue = pendingQueue;1358    queue.pending = null;1359  }13601361  const baseState = hook.baseState;1362  if (baseQueue === null) {1363    // If there are no pending updates, then the memoized state should be the1364    // same as the base state. Currently these only diverge in the case of1365    // useOptimistic, because useOptimistic accepts a new baseState on1366    // every render.1367    hook.memoizedState = baseState;1368    // We don't need to call markWorkInProgressReceivedUpdate because1369    // baseState is derived from other reactive values.1370  } else {1371    // We have a queue to process.1372    const first = baseQueue.next;1373    let newState = baseState;13741375    let newBaseState = null;1376    let newBaseQueueFirst = null;1377    let newBaseQueueLast: Update<S, A> | null = null;1378    let update = first;1379    let didReadFromEntangledAsyncAction = false;1380    do {1381      // An extra OffscreenLane bit is added to updates that were made to1382      // a hidden tree, so that we can distinguish them from updates that were1383      // already there when the tree was hidden.1384      const updateLane = removeLanes(update.lane, OffscreenLane);1385      const isHiddenUpdate = updateLane !== update.lane;13861387      // Check if this update was made while the tree was hidden. If so, then1388      // it's not a "base" update and we should disregard the extra base lanes1389      // that were added to renderLanes when we entered the Offscreen tree.1390      let shouldSkipUpdate = isHiddenUpdate1391        ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)1392        : !isSubsetOfLanes(renderLanes, updateLane);13931394      if (enableGestureTransition && updateLane === GestureLane) {1395        // This is a gesture optimistic update. It should only be considered as part of the1396        // rendered state while rendering the gesture lane and if the rendering the associated1397        // ScheduledGesture.1398        const scheduledGesture = update.gesture;1399        if (scheduledGesture !== null) {1400          if (scheduledGesture.count === 0 && !scheduledGesture.committing) {1401            // This gesture has already been cancelled. We can clean up this update.1402            update = update.next;1403            continue;1404          } else if (!isGestureRender(renderLanes)) {1405            shouldSkipUpdate = true;1406          } else {1407            const root: FiberRoot | null = getWorkInProgressRoot();1408            if (root === null) {1409              throw new Error(1410                'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1411              );1412            }1413            // We assume that the currently rendering gesture is the one first in the queue.1414            shouldSkipUpdate = root.pendingGestures !== scheduledGesture;1415          }1416        }1417      }14181419      if (shouldSkipUpdate) {1420        // Priority is insufficient. Skip this update. If this is the first1421        // skipped update, the previous update/state is the new base1422        // update/state.1423        const clone: Update<S, A> = {1424          lane: updateLane,1425          revertLane: update.revertLane,1426          gesture: update.gesture,1427          action: update.action,1428          hasEagerState: update.hasEagerState,1429          eagerState: update.eagerState,1430          next: null as any,1431        };1432        if (newBaseQueueLast === null) {1433          newBaseQueueFirst = newBaseQueueLast = clone;1434          newBaseState = newState;1435        } else {1436          newBaseQueueLast = newBaseQueueLast.next = clone;1437        }1438        // Update the remaining priority in the queue.1439        // TODO: Don't need to accumulate this. Instead, we can remove1440        // renderLanes from the original lanes.1441        currentlyRenderingFiber.lanes = mergeLanes(1442          currentlyRenderingFiber.lanes,1443          updateLane,1444        );1445        markSkippedUpdateLanes(updateLane);1446      } else {1447        // This update does have sufficient priority.14481449        // Check if this is an optimistic update.1450        const revertLane = update.revertLane;1451        if (revertLane === NoLane) {1452          // This is not an optimistic update, and we're going to apply it now.1453          // But, if there were earlier updates that were skipped, we need to1454          // leave this update in the queue so it can be rebased later.1455          if (newBaseQueueLast !== null) {1456            const clone: Update<S, A> = {1457              // This update is going to be committed so we never want uncommit1458              // it. Using NoLane works because 0 is a subset of all bitmasks, so1459              // this will never be skipped by the check above.1460              lane: NoLane,1461              revertLane: NoLane,1462              gesture: null,1463              action: update.action,1464              hasEagerState: update.hasEagerState,1465              eagerState: update.eagerState,1466              next: null as any,1467            };1468            newBaseQueueLast = newBaseQueueLast.next = clone;1469          }14701471          // Check if this update is part of a pending async action. If so,1472          // we'll need to suspend until the action has finished, so that it's1473          // batched together with future updates in the same action.1474          if (updateLane === peekEntangledActionLane()) {1475            didReadFromEntangledAsyncAction = true;1476          }1477        } else {1478          // This is an optimistic update. If the "revert" priority is1479          // sufficient, don't apply the update. Otherwise, apply the update,1480          // but leave it in the queue so it can be either reverted or1481          // rebased in a subsequent render.1482          if (isSubsetOfLanes(renderLanes, revertLane)) {1483            // The transition that this optimistic update is associated with1484            // has finished. Pretend the update doesn't exist by skipping1485            // over it.1486            update = update.next;14871488            // Check if this update is part of a pending async action. If so,1489            // we'll need to suspend until the action has finished, so that it's1490            // batched together with future updates in the same action.1491            if (revertLane === peekEntangledActionLane()) {1492              didReadFromEntangledAsyncAction = true;1493            }1494            continue;1495          } else {1496            const clone: Update<S, A> = {1497              // Once we commit an optimistic update, we shouldn't uncommit it1498              // until the transition it is associated with has finished1499              // (represented by revertLane). Using NoLane here works because 01500              // is a subset of all bitmasks, so this will never be skipped by1501              // the check above.1502              lane: NoLane,1503              // Reuse the same revertLane so we know when the transition1504              // has finished.1505              revertLane: update.revertLane,1506              gesture: null, // If it commits, it's no longer a gesture update.1507              action: update.action,1508              hasEagerState: update.hasEagerState,1509              eagerState: update.eagerState,1510              next: null as any,1511            };1512            if (newBaseQueueLast === null) {1513              newBaseQueueFirst = newBaseQueueLast = clone;1514              newBaseState = newState;1515            } else {1516              newBaseQueueLast = newBaseQueueLast.next = clone;1517            }1518            // Update the remaining priority in the queue.1519            // TODO: Don't need to accumulate this. Instead, we can remove1520            // renderLanes from the original lanes.1521            currentlyRenderingFiber.lanes = mergeLanes(1522              currentlyRenderingFiber.lanes,1523              revertLane,1524            );1525            markSkippedUpdateLanes(revertLane);1526          }1527        }15281529        // Process this update.1530        const action = update.action;1531        if (shouldDoubleInvokeUserFnsInHooksDEV) {1532          reducer(newState, action);1533        }1534        if (update.hasEagerState) {1535          // If this update is a state update (not a reducer) and was processed eagerly,1536          // we can use the eagerly computed state1537          newState = update.eagerState as any as S;1538        } else {1539          newState = reducer(newState, action);1540        }1541      }1542      update = update.next;1543      // $FlowFixMe[invalid-compare]1544    } while (update !== null && update !== first);15451546    if (newBaseQueueLast === null) {1547      newBaseState = newState;1548    } else {1549      newBaseQueueLast.next = newBaseQueueFirst as any;1550    }15511552    // Mark that the fiber performed work, but only if the new state is1553    // different from the current state.1554    if (!is(newState, hook.memoizedState)) {1555      markWorkInProgressReceivedUpdate();15561557      // Check if this update is part of a pending async action. If so, we'll1558      // need to suspend until the action has finished, so that it's batched1559      // together with future updates in the same action.1560      // TODO: Once we support hooks inside useMemo (or an equivalent1561      // memoization boundary like Forget), hoist this logic so that it only1562      // suspends if the memo boundary produces a new value.1563      if (didReadFromEntangledAsyncAction) {1564        const entangledActionThenable = peekEntangledActionThenable();1565        if (entangledActionThenable !== null) {1566          // TODO: Instead of the throwing the thenable directly, throw a1567          // special object like `use` does so we can detect if it's captured1568          // by userspace.1569          throw entangledActionThenable;1570        }1571      }1572    }15731574    hook.memoizedState = newState;1575    hook.baseState = newBaseState;1576    hook.baseQueue = newBaseQueueLast;15771578    queue.lastRenderedState = newState;1579  }15801581  if (baseQueue === null) {1582    // `queue.lanes` is used for entangling transitions. We can set it back to1583    // zero once the queue is empty.1584    queue.lanes = NoLanes;1585  }15861587  const dispatch: Dispatch<A> = queue.dispatch as any;1588  return [hook.memoizedState, dispatch];1589}15901591function rerenderReducer<S, I, A>(1592  reducer: (S, A) => S,1593  initialArg: I,1594  init?: I => S,1595): [S, Dispatch<A>] {1596  const hook = updateWorkInProgressHook();1597  const queue = hook.queue;15981599  if (queue === null) {1600    throw new Error(1601      'Should have a queue. You are likely calling Hooks conditionally, ' +1602        'which is not allowed. (https://react.dev/link/invalid-hook-call)',1603    );1604  }16051606  queue.lastRenderedReducer = reducer;16071608  // This is a re-render. Apply the new render phase updates to the previous1609  // work-in-progress hook.1610  const dispatch: Dispatch<A> = queue.dispatch as any;1611  const lastRenderPhaseUpdate = queue.pending;1612  let newState = hook.memoizedState;1613  if (lastRenderPhaseUpdate !== null) {1614    // The queue doesn't persist past this render pass.1615    queue.pending = null;16161617    const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;1618    let update = firstRenderPhaseUpdate;1619    do {1620      // Process this render phase update. We don't have to check the1621      // priority because it will always be the same as the current1622      // render's.1623      const action = update.action;1624      newState = reducer(newState, action);1625      update = update.next;1626    } while (update !== firstRenderPhaseUpdate);16271628    // Mark that the fiber performed work, but only if the new state is1629    // different from the current state.1630    if (!is(newState, hook.memoizedState)) {1631      markWorkInProgressReceivedUpdate();1632    }16331634    hook.memoizedState = newState;1635    // Don't persist the state accumulated from the render phase updates to1636    // the base state unless the queue is empty.1637    // TODO: Not sure if this is the desired semantics, but it's what we1638    // do for gDSFP. I can't remember why.1639    if (hook.baseQueue === null) {1640      hook.baseState = newState;1641    }16421643    queue.lastRenderedState = newState;1644  }1645  return [newState, dispatch];1646}16471648function mountSyncExternalStore<T>(1649  subscribe: (() => void) => () => void,1650  getSnapshot: () => T,1651  getServerSnapshot?: () => T,1652): T {1653  const fiber = currentlyRenderingFiber;1654  const hook = mountWorkInProgressHook();16551656  let nextSnapshot;1657  const isHydrating = getIsHydrating();1658  if (isHydrating) {1659    if (getServerSnapshot === undefined) {1660      throw new Error(1661        'Missing getServerSnapshot, which is required for ' +1662          'server-rendered content. Will revert to client rendering.',1663      );1664    }1665    nextSnapshot = getServerSnapshot();1666    if (__DEV__) {1667      if (!didWarnUncachedGetSnapshot) {1668        if (nextSnapshot !== getServerSnapshot()) {1669          console.error(1670            'The result of getServerSnapshot should be cached to avoid an infinite loop',1671          );1672          didWarnUncachedGetSnapshot = true;1673        }1674      }1675    }1676  } else {1677    nextSnapshot = getSnapshot();1678    if (__DEV__) {1679      if (!didWarnUncachedGetSnapshot) {1680        const cachedSnapshot = getSnapshot();1681        if (!is(nextSnapshot, cachedSnapshot)) {1682          console.error(1683            'The result of getSnapshot should be cached to avoid an infinite loop',1684          );1685          didWarnUncachedGetSnapshot = true;1686        }1687      }1688    }1689    // Unless we're rendering a blocking lane, schedule a consistency check.1690    // Right before committing, we will walk the tree and check if any of the1691    // stores were mutated.1692    //1693    // We won't do this if we're hydrating server-rendered content, because if1694    // the content is stale, it's already visible anyway. Instead we'll patch1695    // it up in a passive effect.1696    const root: FiberRoot | null = getWorkInProgressRoot();16971698    if (root === null) {1699      throw new Error(1700        'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1701      );1702    }17031704    const rootRenderLanes = getWorkInProgressRootRenderLanes();1705    if (!includesBlockingLane(rootRenderLanes)) {1706      pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);1707    }1708  }17091710  // Read the current snapshot from the store on every render. This breaks the1711  // normal rules of React, and only works because store updates are1712  // always synchronous.1713  hook.memoizedState = nextSnapshot;1714  const inst: StoreInstance<T> = {1715    value: nextSnapshot,1716    getSnapshot,1717  };1718  hook.queue = inst;17191720  // Schedule an effect to subscribe to the store.1721  mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);17221723  // Schedule an effect to update the mutable instance fields. We will update1724  // this whenever subscribe, getSnapshot, or value changes. Because there's no1725  // clean-up function, and we track the deps correctly, we can call pushEffect1726  // directly, without storing any additional state. For the same reason, we1727  // don't need to set a static flag, either.1728  fiber.flags |= PassiveEffect;1729  pushSimpleEffect(1730    HookHasEffect | HookPassive,1731    createEffectInstance(),1732    updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),1733    null,1734  );17351736  return nextSnapshot;1737}17381739function updateSyncExternalStore<T>(1740  subscribe: (() => void) => () => void,1741  getSnapshot: () => T,1742  getServerSnapshot?: () => T,1743): T {1744  const fiber = currentlyRenderingFiber;1745  const hook = updateWorkInProgressHook();1746  // Read the current snapshot from the store on every render. This breaks the1747  // normal rules of React, and only works because store updates are1748  // always synchronous.1749  let nextSnapshot;1750  const isHydrating = getIsHydrating();1751  if (isHydrating) {1752    // Needed for strict mode double render1753    if (getServerSnapshot === undefined) {1754      throw new Error(1755        'Missing getServerSnapshot, which is required for ' +1756          'server-rendered content. Will revert to client rendering.',1757      );1758    }1759    nextSnapshot = getServerSnapshot();1760  } else {1761    nextSnapshot = getSnapshot();1762    if (__DEV__) {1763      if (!didWarnUncachedGetSnapshot) {1764        const cachedSnapshot = getSnapshot();1765        if (!is(nextSnapshot, cachedSnapshot)) {1766          console.error(1767            'The result of getSnapshot should be cached to avoid an infinite loop',1768          );1769          didWarnUncachedGetSnapshot = true;1770        }1771      }1772    }1773  }1774  const prevSnapshot = (currentHook || hook).memoizedState;1775  const snapshotChanged = !is(prevSnapshot, nextSnapshot);1776  if (snapshotChanged) {1777    hook.memoizedState = nextSnapshot;1778    markWorkInProgressReceivedUpdate();1779  }1780  const inst = hook.queue;17811782  updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [1783    subscribe,1784  ]);17851786  // Whenever getSnapshot or subscribe changes, we need to check in the1787  // commit phase if there was an interleaved mutation. In concurrent mode1788  // this can happen all the time, but even in synchronous mode, an earlier1789  // effect may have mutated the store.1790  const storeChanged =1791    inst.getSnapshot !== getSnapshot ||1792    snapshotChanged ||1793    // Check if the subscribe function changed. We can save some memory by1794    // checking whether we scheduled a subscription effect above.1795    (workInProgressHook !== null &&1796      (workInProgressHook.memoizedState.tag & HookHasEffect) !== HookNoFlags);17971798  // Even if nothing changed during this render, we push the effect so it is1799  // always in the effect list. That way it re-runs whenever the passive1800  // effects are reconnected, like when a hidden Activity tree is shown again.1801  // While the tree was hidden we were not subscribed to the store, so1802  // mutations during that window notified nobody, and if the reveal didn't1803  // re-render this component (or rendered before the mutation), nothing1804  // would ever detect them. When nothing changed, the effect is pushed1805  // without the HasEffect tag so a regular commit skips it.1806  pushSimpleEffect(1807    storeChanged ? HookHasEffect | HookPassive : HookPassive,1808    createEffectInstance(),1809    updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),1810    null,1811  );18121813  if (storeChanged) {1814    fiber.flags |= PassiveEffect;18151816    // Unless we're rendering a blocking lane, schedule a consistency check.1817    // Right before committing, we will walk the tree and check if any of the1818    // stores were mutated.1819    const root: FiberRoot | null = getWorkInProgressRoot();18201821    if (root === null) {1822      throw new Error(1823        'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1824      );1825    }18261827    if (!isHydrating && !includesBlockingLane(renderLanes)) {1828      pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);1829    }1830  }18311832  return nextSnapshot;1833}18341835function pushStoreConsistencyCheck<T>(1836  fiber: Fiber,1837  getSnapshot: () => T,1838  renderedSnapshot: T,1839): void {1840  fiber.flags |= StoreConsistency;1841  const check: StoreConsistencyCheck<T> = {1842    getSnapshot,1843    value: renderedSnapshot,1844  };1845  let componentUpdateQueue: null | FunctionComponentUpdateQueue =1846    currentlyRenderingFiber.updateQueue as any;1847  if (componentUpdateQueue === null) {1848    componentUpdateQueue = createFunctionComponentUpdateQueue();1849    currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;1850    componentUpdateQueue.stores = [check];1851  } else {1852    const stores = componentUpdateQueue.stores;1853    if (stores === null) {1854      componentUpdateQueue.stores = [check];1855    } else {1856      stores.push(check);1857    }1858  }1859}18601861function updateStoreInstance<T>(1862  fiber: Fiber,1863  inst: StoreInstance<T>,1864  nextSnapshot: T,1865  getSnapshot: () => T,1866): void {1867  // These are updated in the passive phase1868  inst.value = nextSnapshot;1869  inst.getSnapshot = getSnapshot;18701871  // Something may have been mutated in between render and commit. This could1872  // have been in an event that fired before the passive effects, or it could1873  // have been in a layout effect. In that case, we would have used the old1874  // snapshot and getSnapshot values to bail out. We need to check one more1875  // time. This effect also re-runs when a hidden Activity tree is revealed.1876  if (checkIfSnapshotChanged(inst)) {1877    // Force a re-render.1878    // We intentionally don't log update times and stacks here because this1879    // was not an external trigger but rather an internal one.1880    forceStoreRerender(fiber);1881  }1882}18831884function subscribeToStore<T>(1885  fiber: Fiber,1886  inst: StoreInstance<T>,1887  subscribe: (() => void) => () => void,1888): any {1889  const handleStoreChange = () => {1890    // The store changed. Check if the snapshot changed since the last time we1891    // read from the store.1892    if (checkIfSnapshotChanged(inst)) {1893      // Force a re-render.1894      startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber);1895      forceStoreRerender(fiber);1896    }1897  };1898  // Subscribe to the store and return a clean-up function.1899  return subscribe(handleStoreChange);1900}19011902function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {1903  const latestGetSnapshot = inst.getSnapshot;1904  const prevValue = inst.value;1905  try {1906    const nextValue = latestGetSnapshot();1907    return !is(prevValue, nextValue);1908  } catch (error) {1909    return true;1910  }1911}19121913function forceStoreRerender(fiber: Fiber) {1914  const root = enqueueConcurrentRenderForLane(fiber, SyncLane);1915  if (root !== null) {1916    scheduleUpdateOnFiber(root, fiber, SyncLane);1917  }1918}19191920function mountStateImpl<S>(initialState: (() => S) | S): Hook {1921  const hook = mountWorkInProgressHook();1922  if (typeof initialState === 'function') {1923    const initialStateInitializer = initialState;1924    // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1925    initialState = initialStateInitializer();1926    if (shouldDoubleInvokeUserFnsInHooksDEV) {1927      setIsStrictModeForDevtools(true);1928      try {1929        // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1930        initialStateInitializer();1931      } finally {1932        setIsStrictModeForDevtools(false);1933      }1934    }1935  }1936  hook.memoizedState = hook.baseState = initialState;1937  const queue: UpdateQueue<S, BasicStateAction<S>> = {1938    pending: null,1939    lanes: NoLanes,1940    dispatch: null,1941    lastRenderedReducer: basicStateReducer,1942    lastRenderedState: initialState as any,1943  };1944  hook.queue = queue;1945  return hook;1946}19471948function mountState<S>(1949  initialState: (() => S) | S,1950): [S, Dispatch<BasicStateAction<S>>] {1951  const hook = mountStateImpl(initialState);1952  const queue = hook.queue;1953  const dispatch: Dispatch<BasicStateAction<S>> = dispatchSetState.bind(1954    null,1955    currentlyRenderingFiber,1956    queue,1957  ) as any;1958  queue.dispatch = dispatch;1959  return [hook.memoizedState, dispatch];1960}19611962function updateState<S>(1963  initialState: (() => S) | S,1964): [S, Dispatch<BasicStateAction<S>>] {1965  return updateReducer(basicStateReducer, initialState);1966}19671968function rerenderState<S>(1969  initialState: (() => S) | S,1970): [S, Dispatch<BasicStateAction<S>>] {1971  return rerenderReducer(basicStateReducer, initialState);1972}19731974function mountOptimistic<S, A>(1975  passthrough: S,1976  reducer: ?(S, A) => S,1977): [S, (A) => void] {1978  const hook = mountWorkInProgressHook();1979  hook.memoizedState = hook.baseState = passthrough;1980  const queue: UpdateQueue<S, A> = {1981    pending: null,1982    lanes: NoLanes,1983    dispatch: null,1984    // Optimistic state does not use the eager update optimization.1985    lastRenderedReducer: null,1986    lastRenderedState: null,1987  };1988  hook.queue = queue;1989  // This is different than the normal setState function.1990  const dispatch: A => void = dispatchOptimisticSetState.bind(1991    null,1992    currentlyRenderingFiber,1993    true,1994    queue,1995  ) as any;1996  queue.dispatch = dispatch;1997  return [passthrough, dispatch];1998}19992000function updateOptimistic<S, A>(

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.