packages/react-reconciler/src/ReactFiberHooks.js JAVASCRIPT 5,242 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,242.
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_MEMO_CACHE_SENTINEL,50} from 'shared/ReactSymbols';5152import {53  NoMode,54  ConcurrentMode,55  StrictEffectsMode,56  StrictLegacyMode,57} from './ReactTypeOfMode';58import {59  NoLane,60  SyncLane,61  OffscreenLane,62  DeferredLane,63  NoLanes,64  isSubsetOfLanes,65  includesBlockingLane,66  includesOnlyNonUrgentLanes,67  mergeLanes,68  removeLanes,69  intersectLanes,70  isTransitionLane,71  markRootEntangled,72  includesSomeLane,73  isGestureRender,74  GestureLane,75  UpdateLanes,76} from './ReactFiberLane';77import {78  ContinuousEventPriority,79  higherEventPriority,80} from './ReactEventPriorities';81import {readContext, checkIfContextChanged} from './ReactFiberNewContext';82import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';83import {84  LayoutStatic as LayoutStaticEffect,85  Passive as PassiveEffect,86  PassiveStatic as PassiveStaticEffect,87  StaticMask as StaticMaskEffect,88  Update as UpdateEffect,89  StoreConsistency,90  MountLayoutDev as MountLayoutDevEffect,91  MountPassiveDev as MountPassiveDevEffect,92  FormReset,93} from './ReactFiberFlags';94import {95  HasEffect as HookHasEffect,96  Layout as HookLayout,97  Passive as HookPassive,98  Insertion as HookInsertion,99} from './ReactHookEffectTags';100import {101  getWorkInProgressRoot,102  getWorkInProgressRootRenderLanes,103  scheduleUpdateOnFiber,104  requestUpdateLane,105  requestDeferredLane,106  markSkippedUpdateLanes,107  isInvalidExecutionContextForEventFunction,108} from './ReactFiberWorkLoop';109110import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';111import is from 'shared/objectIs';112import isArray from 'shared/isArray';113import {114  markWorkInProgressReceivedUpdate,115  checkIfWorkInProgressReceivedUpdate,116} from './ReactFiberBeginWork';117import {118  getIsHydrating,119  tryToClaimNextHydratableFormMarkerInstance,120} from './ReactFiberHydrationContext';121import {122  markStateUpdateScheduled,123  setIsStrictModeForDevtools,124} from './ReactFiberDevToolsHook';125import {126  startUpdateTimerByLane,127  startHostActionTimer,128} from './ReactProfilerTimer';129import {createCache} from './ReactFiberCacheComponent';130import {131  createUpdate as createLegacyQueueUpdate,132  enqueueUpdate as enqueueLegacyQueueUpdate,133  entangleTransitions as entangleLegacyQueueTransitions,134} from './ReactFiberClassUpdateQueue';135import {136  enqueueConcurrentHookUpdate,137  enqueueConcurrentHookUpdateAndEagerlyBailout,138  enqueueConcurrentRenderForLane,139} from './ReactFiberConcurrentUpdates';140import {getTreeId} from './ReactFiberTreeContext';141import {now} from './Scheduler';142import {143  trackUsedThenable,144  checkIfUseWrappedInTryCatch,145  createThenableState,146  SuspenseException,147  SuspenseActionException,148} from './ReactFiberThenable';149import type {ThenableState} from './ReactFiberThenable';150import type {Transition} from 'react/src/ReactStartTransition';151import {152  peekEntangledActionLane,153  peekEntangledActionThenable,154  chainThenableValue,155} from './ReactFiberAsyncAction';156import {requestTransitionLane} from './ReactFiberRootScheduler';157import {isCurrentTreeHidden} from './ReactFiberHiddenContext';158import {requestCurrentTransition} from './ReactFiberTransition';159160import {callComponentInDEV} from './ReactFiberCallUserSpace';161162import {scheduleGesture} from './ReactFiberGestureScheduler';163164export type Update<S, A> = {165  lane: Lane,166  revertLane: Lane,167  action: A,168  hasEagerState: boolean,169  eagerState: S | null,170  next: Update<S, A>,171  gesture: null | ScheduledGesture, // enableGestureTransition172};173174export type UpdateQueue<S, A> = {175  pending: Update<S, A> | null,176  lanes: Lanes,177  dispatch: (A => mixed) | null,178  lastRenderedReducer: ((S, A) => S) | null,179  lastRenderedState: S | null,180};181182let didWarnAboutMismatchedHooksForComponent;183let didWarnUncachedGetSnapshot: void | true;184let didWarnAboutUseWrappedInTryCatch;185let didWarnAboutAsyncClientComponent;186let didWarnAboutUseFormState;187if (__DEV__) {188  didWarnAboutMismatchedHooksForComponent = new Set<string | null>();189  didWarnAboutUseWrappedInTryCatch = new Set<string | null>();190  didWarnAboutAsyncClientComponent = new Set<string | null>();191  didWarnAboutUseFormState = new Set<string | null>();192}193194export type Hook = {195  memoizedState: any,196  baseState: any,197  baseQueue: Update<any, any> | null,198  queue: any,199  next: Hook | null,200};201202// The effect "instance" is a shared object that remains the same for the entire203// lifetime of an effect. In Rust terms, a RefCell. We use it to store the204// "destroy" function that is returned from an effect, because that is stateful.205// The field is `undefined` if the effect is unmounted, or if the effect ran206// but is not stateful. We don't explicitly track whether the effect is mounted207// or unmounted because that can be inferred by the hiddenness of the fiber in208// the tree, i.e. whether there is a hidden Offscreen fiber above it.209//210// It's unfortunate that this is stored on a separate object, because it adds211// more memory per effect instance, but it's conceptually sound. I think there's212// likely a better data structure we could use for effects; perhaps just one213// array of effect instances per fiber. But I think this is OK for now despite214// the additional memory and we can follow up with performance215// optimizations later.216type EffectInstance = {217  destroy: void | (() => void),218};219220export type Effect = {221  tag: HookFlags,222  inst: EffectInstance,223  create: () => (() => void) | void,224  deps: Array<mixed> | void | null,225  next: Effect,226};227228type StoreInstance<T> = {229  value: T,230  getSnapshot: () => T,231};232233type StoreConsistencyCheck<T> = {234  value: T,235  getSnapshot: () => T,236};237238type EventFunctionPayload<Args, Return, F: (...Array<Args>) => Return> = {239  ref: {240    eventFn: F,241    impl: F,242  },243  nextImpl: F,244};245246export type FunctionComponentUpdateQueue = {247  lastEffect: Effect | null,248  events: Array<EventFunctionPayload<any, any, any>> | null,249  stores: Array<StoreConsistencyCheck<any>> | null,250  memoCache: MemoCache | null,251};252253type BasicStateAction<S> = (S => S) | S;254255type Dispatch<A> = A => void;256257// These are set right before calling the component.258let renderLanes: Lanes = NoLanes;259// The work-in-progress fiber. I've named it differently to distinguish it from260// the work-in-progress hook.261let currentlyRenderingFiber: Fiber = null as any;262263// Hooks are stored as a linked list on the fiber's memoizedState field. The264// current hook list is the list that belongs to the current fiber. The265// work-in-progress hook list is a new list that will be added to the266// work-in-progress fiber.267let currentHook: Hook | null = null;268let workInProgressHook: Hook | null = null;269270// Whether an update was scheduled at any point during the render phase. This271// does not get reset if we do another render pass; only when we're completely272// finished evaluating this component. This is an optimization so we know273// whether we need to clear render phase updates after a throw.274let didScheduleRenderPhaseUpdate: boolean = false;275// Where an update was scheduled only during the current render pass. This276// gets reset after each attempt.277// TODO: Maybe there's some way to consolidate this with278// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.279let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;280let shouldDoubleInvokeUserFnsInHooksDEV: boolean = false;281// Counts the number of useId hooks in this component.282let localIdCounter: number = 0;283// Counts number of `use`-d thenables284let thenableIndexCounter: number = 0;285let thenableState: ThenableState | null = null;286287// Used for ids that are generated completely client-side (i.e. not during288// hydration). This counter is global, so client ids are not stable across289// render attempts.290let globalClientIdCounter: number = 0;291292const RE_RENDER_LIMIT = 25;293294// In DEV, this is the name of the currently executing primitive hook295let currentHookNameInDev: ?HookType = null;296297// In DEV, this list ensures that hooks are called in the same order between renders.298// The list stores the order of hooks used during the initial render (mount).299// Subsequent renders (updates) reference this list.300let hookTypesDev: Array<HookType> | null = null;301let hookTypesUpdateIndexDev: number = -1;302303// In DEV, this tracks whether currently rendering component needs to ignore304// the dependencies for Hooks that need them (e.g. useEffect or useMemo).305// When true, such Hooks will always be "remounted". Only used during hot reload.306let ignorePreviousDependencies: boolean = false;307308function mountHookTypesDev(): void {309  if (__DEV__) {310    const hookName = currentHookNameInDev as any as HookType;311312    if (hookTypesDev === null) {313      hookTypesDev = [hookName];314    } else {315      hookTypesDev.push(hookName);316    }317  }318}319320function updateHookTypesDev(): void {321  if (__DEV__) {322    const hookName = currentHookNameInDev as any as HookType;323324    if (hookTypesDev !== null) {325      hookTypesUpdateIndexDev++;326      if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {327        warnOnHookMismatchInDev(hookName);328      }329    }330  }331}332333function checkDepsAreArrayDev(deps: mixed): void {334  if (__DEV__) {335    if (deps !== undefined && deps !== null && !isArray(deps)) {336      // Verify deps, but only on mount to avoid extra checks.337      // It's unlikely their type would change as usually you define them inline.338      console.error(339        '%s received a final argument that is not an array (instead, received `%s`). When ' +340          'specified, the final argument must be an array.',341        currentHookNameInDev,342        typeof deps,343      );344    }345  }346}347348function warnOnHookMismatchInDev(currentHookName: HookType): void {349  if (__DEV__) {350    const componentName = getComponentNameFromFiber(currentlyRenderingFiber);351    if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {352      didWarnAboutMismatchedHooksForComponent.add(componentName);353354      if (hookTypesDev !== null) {355        let table = '';356357        const secondColumnStart = 30;358359        for (let i = 0; i <= (hookTypesUpdateIndexDev as any as number); i++) {360          const oldHookName = hookTypesDev[i];361          const newHookName =362            i === (hookTypesUpdateIndexDev as any as number)363              ? currentHookName364              : oldHookName;365366          let row = `${i + 1}. ${oldHookName}`;367368          // Extra space so second column lines up369          // lol @ IE not supporting String#repeat370          while (row.length < secondColumnStart) {371            row += ' ';372          }373374          row += newHookName + '\n';375376          table += row;377        }378379        console.error(380          'React has detected a change in the order of Hooks called by %s. ' +381            'This will lead to bugs and errors if not fixed. ' +382            'For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +383            '   Previous render            Next render\n' +384            '   ------------------------------------------------------\n' +385            '%s' +386            '   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',387          componentName,388          table,389        );390      }391    }392  }393}394395function warnOnUseFormStateInDev(): void {396  if (__DEV__) {397    const componentName = getComponentNameFromFiber(currentlyRenderingFiber);398    if (!didWarnAboutUseFormState.has(componentName)) {399      didWarnAboutUseFormState.add(componentName);400401      console.error(402        'ReactDOM.useFormState has been renamed to React.useActionState. ' +403          'Please update %s to use React.useActionState.',404        componentName,405      );406    }407  }408}409410function warnIfAsyncClientComponent(Component: Function) {411  if (__DEV__) {412    // This dev-only check only works for detecting native async functions,413    // not transpiled ones. There's also a prod check that we use to prevent414    // async client components from crashing the app; the prod one works even415    // for transpiled async functions. Neither mechanism is completely416    // bulletproof but together they cover the most common cases.417    const isAsyncFunction =418      // $FlowFixMe[method-unbinding]419      Object.prototype.toString.call(Component) === '[object AsyncFunction]' ||420      // $FlowFixMe[method-unbinding]421      Object.prototype.toString.call(Component) ===422        '[object AsyncGeneratorFunction]';423    if (isAsyncFunction) {424      // Encountered an async Client Component. This is not yet supported.425      const componentName = getComponentNameFromFiber(currentlyRenderingFiber);426      if (!didWarnAboutAsyncClientComponent.has(componentName)) {427        didWarnAboutAsyncClientComponent.add(componentName);428        console.error(429          '%s is an async Client Component. ' +430            'Only Server Components can be async at the moment. This error is often caused by accidentally ' +431            "adding `'use client'` to a module that was originally written " +432            'for the server.',433          componentName === null434            ? 'An unknown Component'435            : `<${componentName}>`,436        );437      }438    }439  }440}441442function throwInvalidHookError() {443  throw new Error(444    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +445      ' one of the following reasons:\n' +446      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +447      '2. You might be breaking the Rules of Hooks\n' +448      '3. You might have more than one copy of React in the same app\n' +449      'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',450  );451}452453function areHookInputsEqual(454  nextDeps: Array<mixed>,455  prevDeps: Array<mixed> | null,456): boolean {457  if (__DEV__) {458    if (ignorePreviousDependencies) {459      // Only true when this component is being hot reloaded.460      return false;461    }462  }463464  if (prevDeps === null) {465    if (__DEV__) {466      console.error(467        '%s received a final argument during this render, but not during ' +468          'the previous render. Even though the final argument is optional, ' +469          'its type cannot change between renders.',470        currentHookNameInDev,471      );472    }473    return false;474  }475476  if (__DEV__) {477    // Don't bother comparing lengths in prod because these arrays should be478    // passed inline.479    if (nextDeps.length !== prevDeps.length) {480      console.error(481        'The final argument passed to %s changed size between renders. The ' +482          'order and size of this array must remain constant.\n\n' +483          'Previous: %s\n' +484          'Incoming: %s',485        currentHookNameInDev,486        `[${prevDeps.join(', ')}]`,487        `[${nextDeps.join(', ')}]`,488      );489    }490  }491  // $FlowFixMe[incompatible-use] found when upgrading Flow492  for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {493    // $FlowFixMe[incompatible-use] found when upgrading Flow494    if (is(nextDeps[i], prevDeps[i])) {495      continue;496    }497    return false;498  }499  return true;500}501502export function renderWithHooks<Props, SecondArg>(503  current: Fiber | null,504  workInProgress: Fiber,505  Component: (p: Props, arg: SecondArg) => any,506  props: Props,507  secondArg: SecondArg,508  nextRenderLanes: Lanes,509): any {510  renderLanes = nextRenderLanes;511  currentlyRenderingFiber = workInProgress;512513  if (__DEV__) {514    hookTypesDev =515      current !== null516        ? (current._debugHookTypes as any as Array<HookType>)517        : null;518    hookTypesUpdateIndexDev = -1;519    // Used for hot reloading:520    ignorePreviousDependencies =521      current !== null && current.type !== workInProgress.type;522523    warnIfAsyncClientComponent(Component);524  }525526  workInProgress.memoizedState = null;527  workInProgress.updateQueue = null;528  workInProgress.lanes = NoLanes;529530  // The following should have already been reset531  // currentHook = null;532  // workInProgressHook = null;533534  // didScheduleRenderPhaseUpdate = false;535  // localIdCounter = 0;536  // thenableIndexCounter = 0;537  // thenableState = null;538539  // TODO Warn if no hooks are used at all during mount, then some are used during update.540  // Currently we will identify the update render as a mount because memoizedState === null.541  // This is tricky because it's valid for certain types of components (e.g. React.lazy)542543  // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.544  // Non-stateful hooks (e.g. context) don't get added to memoizedState,545  // so memoizedState would be null during updates and mounts.546  if (__DEV__) {547    if (current !== null && current.memoizedState !== null) {548      ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;549    } else if (hookTypesDev !== null) {550      // This dispatcher handles an edge case where a component is updating,551      // but no stateful hooks have been used.552      // We want to match the production code behavior (which will use HooksDispatcherOnMount),553      // but with the extra DEV validation to ensure hooks ordering hasn't changed.554      // This dispatcher does that.555      ReactSharedInternals.H = HooksDispatcherOnMountWithHookTypesInDEV;556    } else {557      ReactSharedInternals.H = HooksDispatcherOnMountInDEV;558    }559  } else {560    ReactSharedInternals.H =561      current === null || current.memoizedState === null562        ? HooksDispatcherOnMount563        : HooksDispatcherOnUpdate;564  }565566  // In Strict Mode, during development, user functions are double invoked to567  // help detect side effects. The logic for how this is implemented for in568  // hook components is a bit complex so let's break it down.569  //570  // We will invoke the entire component function twice. However, during the571  // second invocation of the component, the hook state from the first572  // invocation will be reused. That means things like `useMemo` functions won't573  // run again, because the deps will match and the memoized result will574  // be reused.575  //576  // We want memoized functions to run twice, too, so account for this, user577  // functions are double invoked during the *first* invocation of the component578  // function, and are *not* double invoked during the second incovation:579  //580  // - First execution of component function: user functions are double invoked581  // - Second execution of component function (in Strict Mode, during582  //   development): user functions are not double invoked.583  //584  // This is intentional for a few reasons; most importantly, it's because of585  // how `use` works when something suspends: it reuses the promise that was586  // passed during the first attempt. This is itself a form of memoization.587  // We need to be able to memoize the reactive inputs to the `use` call using588  // a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must589  // come from the same component invocation as the output.590  //591  // There are plenty of tests to ensure this behavior is correct.592  const shouldDoubleRenderDEV =593    __DEV__ && (workInProgress.mode & StrictLegacyMode) !== NoMode;594595  shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;596  let children = __DEV__597    ? callComponentInDEV(Component, props, secondArg)598    : Component(props, secondArg);599  shouldDoubleInvokeUserFnsInHooksDEV = false;600601  // Check if there was a render phase update602  if (didScheduleRenderPhaseUpdateDuringThisPass) {603    // Keep rendering until the component stabilizes (there are no more render604    // phase updates).605    children = renderWithHooksAgain(606      workInProgress,607      Component,608      props,609      secondArg,610    );611  }612613  if (shouldDoubleRenderDEV) {614    // In development, components are invoked twice to help detect side effects.615    setIsStrictModeForDevtools(true);616    try {617      children = renderWithHooksAgain(618        workInProgress,619        Component,620        props,621        secondArg,622      );623    } finally {624      setIsStrictModeForDevtools(false);625    }626  }627628  finishRenderingHooks(current, workInProgress, Component);629630  return children;631}632633function finishRenderingHooks<Props, SecondArg>(634  current: Fiber | null,635  workInProgress: Fiber,636  Component: (p: Props, arg: SecondArg) => any,637): void {638  if (__DEV__) {639    workInProgress._debugHookTypes = hookTypesDev;640    // Stash the thenable state for use by DevTools.641    if (workInProgress.dependencies === null) {642      if (thenableState !== null) {643        workInProgress.dependencies = {644          lanes: NoLanes,645          firstContext: null,646          _debugThenableState: thenableState,647        };648      }649    } else {650      workInProgress.dependencies._debugThenableState = thenableState;651    }652  }653654  // We can assume the previous dispatcher is always this one, since we set it655  // at the beginning of the render phase and there's no re-entrance.656  ReactSharedInternals.H = ContextOnlyDispatcher;657658  // This check uses currentHook so that it works the same in DEV and prod bundles.659  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.660  const didRenderTooFewHooks =661    currentHook !== null && currentHook.next !== null;662663  renderLanes = NoLanes;664  currentlyRenderingFiber = null as any;665666  currentHook = null;667  workInProgressHook = null;668669  if (__DEV__) {670    currentHookNameInDev = null;671    hookTypesDev = null;672    hookTypesUpdateIndexDev = -1;673674    // Confirm that a static flag was not added or removed since the last675    // render. If this fires, it suggests that we incorrectly reset the static676    // flags in some other part of the codebase. This has happened before, for677    // example, in the SuspenseList implementation.678    if (679      current !== null &&680      (current.flags & StaticMaskEffect) !==681        (workInProgress.flags & StaticMaskEffect) &&682      // Disable this warning in legacy mode, because legacy Suspense is weird683      // and creates false positives. To make this work in legacy mode, we'd684      // need to mark fibers that commit in an incomplete state, somehow. For685      // now I'll disable the warning that most of the bugs that would trigger686      // it are either exclusive to concurrent mode or exist in both.687      (disableLegacyMode || (current.mode & ConcurrentMode) !== NoMode)688    ) {689      console.error(690        'Internal React error: Expected static flag was missing. Please ' +691          'notify the React team.',692      );693    }694  }695696  didScheduleRenderPhaseUpdate = false;697  // This is reset by checkDidRenderIdHook698  // localIdCounter = 0;699700  thenableIndexCounter = 0;701  thenableState = null;702703  if (didRenderTooFewHooks) {704    throw new Error(705      'Rendered fewer hooks than expected. This may be caused by an accidental ' +706        'early return statement.',707    );708  }709710  if (current !== null) {711    if (!checkIfWorkInProgressReceivedUpdate()) {712      // If there were no changes to props or state, we need to check if there713      // was a context change. We didn't already do this because there's no714      // 1:1 correspondence between dependencies and hooks. Although, because715      // there almost always is in the common case (`readContext` is an716      // internal API), we could compare in there. OTOH, we only hit this case717      // if everything else bails out, so on the whole it might be better to718      // keep the comparison out of the common path.719      const currentDependencies = current.dependencies;720      if (721        currentDependencies !== null &&722        checkIfContextChanged(currentDependencies)723      ) {724        markWorkInProgressReceivedUpdate();725      }726    }727  }728729  if (__DEV__) {730    if (checkIfUseWrappedInTryCatch()) {731      const componentName =732        getComponentNameFromFiber(workInProgress) || 'Unknown';733      if (734        !didWarnAboutUseWrappedInTryCatch.has(componentName) &&735        // This warning also fires if you suspend with `use` inside an736        // async component. Since we warn for that above, we'll silence this737        // second warning by checking here.738        !didWarnAboutAsyncClientComponent.has(componentName)739      ) {740        didWarnAboutUseWrappedInTryCatch.add(componentName);741        console.error(742          '`use` was called from inside a try/catch block. This is not allowed ' +743            'and can lead to unexpected behavior. To handle errors triggered ' +744            'by `use`, wrap your component in a error boundary.',745        );746      }747    }748  }749}750751export function replaySuspendedComponentWithHooks<Props, SecondArg>(752  current: Fiber | null,753  workInProgress: Fiber,754  Component: (p: Props, arg: SecondArg) => any,755  props: Props,756  secondArg: SecondArg,757): any {758  // This function is used to replay a component that previously suspended,759  // after its data resolves.760  //761  // It's a simplified version of renderWithHooks, but it doesn't need to do762  // most of the set up work because they weren't reset when we suspended; they763  // only get reset when the component either completes (finishRenderingHooks)764  // or unwinds (resetHooksOnUnwind).765  if (__DEV__) {766    hookTypesUpdateIndexDev = -1;767    // Used for hot reloading:768    ignorePreviousDependencies =769      current !== null && current.type !== workInProgress.type;770  }771  // renderWithHooks only resets the updateQueue but does not clear it, since772  // it needs to work for both this case (suspense replay) as well as for double773  // renders in dev and setState-in-render. However, for the suspense replay case774  // we need to reset the updateQueue to correctly handle unmount effects, so we775  // clear the queue here776  workInProgress.updateQueue = null;777  const children = renderWithHooksAgain(778    workInProgress,779    Component,780    props,781    secondArg,782  );783  finishRenderingHooks(current, workInProgress, Component);784  return children;785}786787function renderWithHooksAgain<Props, SecondArg>(788  workInProgress: Fiber,789  Component: (p: Props, arg: SecondArg) => any,790  props: Props,791  secondArg: SecondArg,792): any {793  // This is used to perform another render pass. It's used when setState is794  // called during render, and for double invoking components in Strict Mode795  // during development.796  //797  // The state from the previous pass is reused whenever possible. So, state798  // updates that were already processed are not processed again, and memoized799  // functions (`useMemo`) are not invoked again.800  //801  // Keep rendering in a loop for as long as render phase updates continue to802  // be scheduled. Use a counter to prevent infinite loops.803804  currentlyRenderingFiber = workInProgress;805806  let numberOfReRenders: number = 0;807  let children;808  do {809    if (didScheduleRenderPhaseUpdateDuringThisPass) {810      // It's possible that a use() value depended on a state that was updated in811      // this rerender, so we need to watch for different thenables this time.812      thenableState = null;813    }814    thenableIndexCounter = 0;815    didScheduleRenderPhaseUpdateDuringThisPass = false;816817    if (numberOfReRenders >= RE_RENDER_LIMIT) {818      throw new Error(819        'Too many re-renders. React limits the number of renders to prevent ' +820          'an infinite loop.',821      );822    }823824    numberOfReRenders += 1;825    if (__DEV__) {826      // Even when hot reloading, allow dependencies to stabilize827      // after first render to prevent infinite render phase updates.828      ignorePreviousDependencies = false;829    }830831    // Start over from the beginning of the list832    currentHook = null;833    workInProgressHook = null;834835    if (workInProgress.updateQueue != null) {836      resetFunctionComponentUpdateQueue(workInProgress.updateQueue as any);837    }838839    if (__DEV__) {840      // Also validate hook order for cascading updates.841      hookTypesUpdateIndexDev = -1;842    }843844    ReactSharedInternals.H = __DEV__845      ? HooksDispatcherOnRerenderInDEV846      : HooksDispatcherOnRerender;847848    children = __DEV__849      ? callComponentInDEV(Component, props, secondArg)850      : Component(props, secondArg);851  } while (didScheduleRenderPhaseUpdateDuringThisPass);852  return children;853}854855export function renderTransitionAwareHostComponentWithHooks(856  current: Fiber | null,857  workInProgress: Fiber,858  lanes: Lanes,859): TransitionStatus {860  return renderWithHooks(861    current,862    workInProgress,863    TransitionAwareHostComponent,864    null,865    null,866    lanes,867  );868}869870export function TransitionAwareHostComponent(): TransitionStatus {871  const dispatcher: any = ReactSharedInternals.H;872  const [maybeThenable] = dispatcher.useState();873  let nextState;874  if (typeof maybeThenable.then === 'function') {875    const thenable: Thenable<TransitionStatus> = maybeThenable as any;876    nextState = useThenable(thenable);877  } else {878    const status: TransitionStatus = maybeThenable;879    nextState = status;880  }881882  // The "reset state" is an object. If it changes, that means something883  // requested that we reset the form.884  const [nextResetState] = dispatcher.useState();885  const prevResetState =886    currentHook !== null ? currentHook.memoizedState : null;887  if (prevResetState !== nextResetState) {888    // Schedule a form reset889    currentlyRenderingFiber.flags |= FormReset;890  }891892  return nextState;893}894895export function checkDidRenderIdHook(): boolean {896  // This should be called immediately after every renderWithHooks call.897  // Conceptually, it's part of the return value of renderWithHooks; it's only a898  // separate function to avoid using an array tuple.899  const didRenderIdHook = localIdCounter !== 0;900  localIdCounter = 0;901  return didRenderIdHook;902}903904export function bailoutHooks(905  current: Fiber,906  workInProgress: Fiber,907  lanes: Lanes,908): void {909  workInProgress.updateQueue = current.updateQueue;910  // TODO: Don't need to reset the flags here, because they're reset in the911  // complete phase (bubbleProperties).912  if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {913    workInProgress.flags &= ~(914      MountPassiveDevEffect |915      MountLayoutDevEffect |916      PassiveEffect |917      UpdateEffect918    );919  } else {920    workInProgress.flags &= ~(PassiveEffect | UpdateEffect);921  }922  current.lanes = removeLanes(current.lanes, lanes);923}924925export function resetHooksAfterThrow(): void {926  // This is called immediaetly after a throw. It shouldn't reset the entire927  // module state, because the work loop might decide to replay the component928  // again without rewinding.929  //930  // It should only reset things like the current dispatcher, to prevent hooks931  // from being called outside of a component.932  currentlyRenderingFiber = null as any;933934  // We can assume the previous dispatcher is always this one, since we set it935  // at the beginning of the render phase and there's no re-entrance.936  ReactSharedInternals.H = ContextOnlyDispatcher;937}938939export function resetHooksOnUnwind(workInProgress: Fiber): void {940  if (didScheduleRenderPhaseUpdate) {941    // There were render phase updates. These are only valid for this render942    // phase, which we are now aborting. Remove the updates from the queues so943    // they do not persist to the next render. Do not remove updates from hooks944    // that weren't processed.945    //946    // Only reset the updates from the queue if it has a clone. If it does947    // not have a clone, that means it wasn't processed, and the updates were948    // scheduled before we entered the render phase.949    let hook: Hook | null = workInProgress.memoizedState;950    while (hook !== null) {951      const queue = hook.queue;952      if (queue !== null) {953        queue.pending = null;954      }955      hook = hook.next;956    }957    didScheduleRenderPhaseUpdate = false;958  }959960  renderLanes = NoLanes;961  currentlyRenderingFiber = null as any;962963  currentHook = null;964  workInProgressHook = null;965966  if (__DEV__) {967    hookTypesDev = null;968    hookTypesUpdateIndexDev = -1;969970    currentHookNameInDev = null;971  }972973  didScheduleRenderPhaseUpdateDuringThisPass = false;974  localIdCounter = 0;975  thenableIndexCounter = 0;976  thenableState = null;977}978979function mountWorkInProgressHook(): Hook {980  const hook: Hook = {981    memoizedState: null,982983    baseState: null,984    baseQueue: null,985    queue: null,986987    next: null,988  };989990  if (workInProgressHook === null) {991    // This is the first hook in the list992    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;993  } else {994    // Append to the end of the list995    workInProgressHook = workInProgressHook.next = hook;996  }997  return workInProgressHook;998}9991000function updateWorkInProgressHook(): Hook {1001  // This function is used both for updates and for re-renders triggered by a1002  // render phase update. It assumes there is either a current hook we can1003  // clone, or a work-in-progress hook from a previous render pass that we can1004  // use as a base.1005  let nextCurrentHook: null | Hook;1006  if (currentHook === null) {1007    const current = currentlyRenderingFiber.alternate;1008    if (current !== null) {1009      nextCurrentHook = current.memoizedState;1010    } else {1011      nextCurrentHook = null;1012    }1013  } else {1014    nextCurrentHook = currentHook.next;1015  }10161017  let nextWorkInProgressHook: null | Hook;1018  if (workInProgressHook === null) {1019    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;1020  } else {1021    nextWorkInProgressHook = workInProgressHook.next;1022  }10231024  if (nextWorkInProgressHook !== null) {1025    // There's already a work-in-progress. Reuse it.1026    workInProgressHook = nextWorkInProgressHook;1027    nextWorkInProgressHook = workInProgressHook.next;10281029    currentHook = nextCurrentHook;1030  } else {1031    // Clone from the current hook.10321033    if (nextCurrentHook === null) {1034      const currentFiber = currentlyRenderingFiber.alternate;1035      if (currentFiber === null) {1036        // This is the initial render. This branch is reached when the component1037        // suspends, resumes, then renders an additional hook.1038        // Should never be reached because we should switch to the mount dispatcher first.1039        throw new Error(1040          'Update hook called on initial render. This is likely a bug in React. Please file an issue.',1041        );1042      } else {1043        // This is an update. We should always have a current hook.1044        throw new Error('Rendered more hooks than during the previous render.');1045      }1046    }10471048    currentHook = nextCurrentHook;10491050    const newHook: Hook = {1051      memoizedState: currentHook.memoizedState,10521053      baseState: currentHook.baseState,1054      baseQueue: currentHook.baseQueue,1055      queue: currentHook.queue,10561057      next: null,1058    };10591060    if (workInProgressHook === null) {1061      // This is the first hook in the list.1062      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;1063    } else {1064      // Append to the end of the list.1065      workInProgressHook = workInProgressHook.next = newHook;1066    }1067  }1068  return workInProgressHook;1069}10701071function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {1072  return {1073    lastEffect: null,1074    events: null,1075    stores: null,1076    memoCache: null,1077  };1078}10791080function resetFunctionComponentUpdateQueue(1081  updateQueue: FunctionComponentUpdateQueue,1082): void {1083  updateQueue.lastEffect = null;1084  updateQueue.events = null;1085  updateQueue.stores = null;1086  if (updateQueue.memoCache != null) {1087    // NOTE: this function intentionally does not reset memoCache data. We reuse updateQueue for the memo1088    // cache to avoid increasing the size of fibers that don't need a cache, but we don't want to reset1089    // the cache when other properties are reset.1090    updateQueue.memoCache.index = 0;1091  }1092}10931094function useThenable<T>(thenable: Thenable<T>): T {1095  // Track the position of the thenable within this fiber.1096  const index = thenableIndexCounter;1097  thenableIndexCounter += 1;1098  if (thenableState === null) {1099    thenableState = createThenableState();1100  }1101  const result = trackUsedThenable(thenableState, thenable, index);11021103  // When something suspends with `use`, we replay the component with the1104  // "re-render" dispatcher instead of the "mount" or "update" dispatcher.1105  //1106  // But if there are additional hooks that occur after the `use` invocation1107  // that suspended, they wouldn't have been processed during the previous1108  // attempt. So after we invoke `use` again, we may need to switch from the1109  // "re-render" dispatcher back to the "mount" or "update" dispatcher. That's1110  // what the following logic accounts for.1111  //1112  // TODO: Theoretically this logic only needs to go into the rerender1113  // dispatcher. Could optimize, but probably not be worth it.11141115  // This is the same logic as in updateWorkInProgressHook.1116  const workInProgressFiber = currentlyRenderingFiber;1117  const nextWorkInProgressHook =1118    workInProgressHook === null1119      ? // We're at the beginning of the list, so read from the first hook from1120        // the fiber.1121        workInProgressFiber.memoizedState1122      : workInProgressHook.next;11231124  if (nextWorkInProgressHook !== null) {1125    // There are still hooks remaining from the previous attempt.1126  } else {1127    // There are no remaining hooks from the previous attempt. We're no longer1128    // in "re-render" mode. Switch to the normal mount or update dispatcher.1129    //1130    // This is the same as the logic in renderWithHooks, except we don't bother1131    // to track the hook types debug information in this case (sufficient to1132    // only do that when nothing suspends).1133    const currentFiber = workInProgressFiber.alternate;1134    if (__DEV__) {1135      if (currentFiber !== null && currentFiber.memoizedState !== null) {1136        ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;1137      } else {1138        ReactSharedInternals.H = HooksDispatcherOnMountInDEV;1139      }1140    } else {1141      ReactSharedInternals.H =1142        currentFiber === null || currentFiber.memoizedState === null1143          ? HooksDispatcherOnMount1144          : HooksDispatcherOnUpdate;1145    }1146  }1147  return result;1148}11491150function use<T>(usable: Usable<T>): T {1151  // $FlowFixMe[invalid-compare]1152  if (usable !== null && typeof usable === 'object') {1153    // $FlowFixMe[method-unbinding]1154    if (typeof usable.then === 'function') {1155      // This is a thenable.1156      const thenable: Thenable<T> = usable as any;1157      return useThenable(thenable);1158    } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {1159      const context: ReactContext<T> = usable as any;1160      return readContext(context);1161    }1162  }11631164  // eslint-disable-next-line react-internal/safe-string-coercion1165  throw new Error('An unsupported type was passed to use(): ' + String(usable));1166}11671168function useMemoCache(size: number): Array<mixed> {1169  let memoCache = null;1170  // Fast-path, load memo cache from wip fiber if already prepared1171  let updateQueue: FunctionComponentUpdateQueue | null =1172    currentlyRenderingFiber.updateQueue as any;1173  if (updateQueue !== null) {1174    memoCache = updateQueue.memoCache;1175  }1176  // Otherwise clone from the current fiber1177  if (memoCache == null) {1178    const current: Fiber | null = currentlyRenderingFiber.alternate;1179    if (current !== null) {1180      const currentUpdateQueue: FunctionComponentUpdateQueue | null =1181        current.updateQueue as any;1182      if (currentUpdateQueue !== null) {1183        const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;1184        if (currentMemoCache != null) {1185          memoCache = {1186            // When enableNoCloningMemoCache is enabled, instead of treating the1187            // cache as copy-on-write, like we do with fibers, we share the same1188            // cache instance across all render attempts, even if the component1189            // is interrupted before it commits.1190            //1191            // If an update is interrupted, either because it suspended or1192            // because of another update, we can reuse the memoized computations1193            // from the previous attempt. We can do this because the React1194            // Compiler performs atomic writes to the memo cache, i.e. it will1195            // not record the inputs to a memoization without also recording its1196            // output.1197            //1198            // This gives us a form of "resuming" within components and hooks.1199            //1200            // This only works when updating a component that already mounted.1201            // It has no impact during initial render, because the memo cache is1202            // stored on the fiber, and since we have not implemented resuming1203            // for fibers, it's always a fresh memo cache, anyway.1204            //1205            // However, this alone is pretty useful — it happens whenever you1206            // update the UI with fresh data after a mutation/action, which is1207            // extremely common in a Suspense-driven (e.g. RSC or Relay) app.1208            data: enableNoCloningMemoCache1209              ? currentMemoCache.data1210              : // Clone the memo cache before each render (copy-on-write)1211                currentMemoCache.data.map(array => array.slice()),1212            index: 0 as number,1213          };1214        }1215      }1216    }1217  }1218  // Finally fall back to allocating a fresh instance of the cache1219  if (memoCache == null) {1220    memoCache = {1221      data: [],1222      index: 0 as number,1223    };1224  }1225  if (updateQueue === null) {1226    updateQueue = createFunctionComponentUpdateQueue();1227    currentlyRenderingFiber.updateQueue = updateQueue;1228  }1229  updateQueue.memoCache = memoCache;12301231  let data = memoCache.data[memoCache.index];1232  if (data === undefined || (__DEV__ && ignorePreviousDependencies)) {1233    data = memoCache.data[memoCache.index] = new Array(size);1234    for (let i = 0; i < size; i++) {1235      data[i] = REACT_MEMO_CACHE_SENTINEL;1236    }1237  } else if (data.length !== size) {1238    // TODO: consider warning or throwing here1239    if (__DEV__) {1240      console.error(1241        'Expected a constant size argument for each invocation of useMemoCache. ' +1242          'The previous cache was allocated with size %s but size %s was requested.',1243        data.length,1244        size,1245      );1246    }1247  }1248  memoCache.index++;1249  return data;1250}12511252function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {1253  // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1254  return typeof action === 'function' ? action(state) : action;1255}12561257function mountReducer<S, I, A>(1258  reducer: (S, A) => S,1259  initialArg: I,1260  init?: I => S,1261): [S, Dispatch<A>] {1262  const hook = mountWorkInProgressHook();1263  let initialState;1264  if (init !== undefined) {1265    initialState = init(initialArg);1266    if (shouldDoubleInvokeUserFnsInHooksDEV) {1267      setIsStrictModeForDevtools(true);1268      try {1269        init(initialArg);1270      } finally {1271        setIsStrictModeForDevtools(false);1272      }1273    }1274  } else {1275    initialState = initialArg as any as S;1276  }1277  hook.memoizedState = hook.baseState = initialState;1278  const queue: UpdateQueue<S, A> = {1279    pending: null,1280    lanes: NoLanes,1281    dispatch: null,1282    lastRenderedReducer: reducer,1283    lastRenderedState: initialState as any,1284  };1285  hook.queue = queue;1286  const dispatch: Dispatch<A> = (queue.dispatch = dispatchReducerAction.bind(1287    null,1288    currentlyRenderingFiber,1289    queue,1290  ) as any);1291  return [hook.memoizedState, dispatch];1292}12931294function updateReducer<S, I, A>(1295  reducer: (S, A) => S,1296  initialArg: I,1297  init?: I => S,1298): [S, Dispatch<A>] {1299  const hook = updateWorkInProgressHook();1300  return updateReducerImpl(hook, currentHook as any as Hook, reducer);1301}13021303function updateReducerImpl<S, A>(1304  hook: Hook,1305  current: Hook,1306  reducer: (S, A) => S,1307): [S, Dispatch<A>] {1308  const queue = hook.queue;13091310  if (queue === null) {1311    throw new Error(1312      'Should have a queue. You are likely calling Hooks conditionally, ' +1313        'which is not allowed. (https://react.dev/link/invalid-hook-call)',1314    );1315  }13161317  queue.lastRenderedReducer = reducer;13181319  // The last rebase update that is NOT part of the base state.1320  let baseQueue = hook.baseQueue;13211322  // The last pending update that hasn't been processed yet.1323  const pendingQueue = queue.pending;1324  if (pendingQueue !== null) {1325    // We have new updates that haven't been processed yet.1326    // We'll add them to the base queue.1327    if (baseQueue !== null) {1328      // Merge the pending queue and the base queue.1329      const baseFirst = baseQueue.next;1330      const pendingFirst = pendingQueue.next;1331      baseQueue.next = pendingFirst;1332      pendingQueue.next = baseFirst;1333    }1334    if (__DEV__) {1335      if (current.baseQueue !== baseQueue) {1336        // Internal invariant that should never happen, but feasibly could in1337        // the future if we implement resuming, or some form of that.1338        console.error(1339          'Internal error: Expected work-in-progress queue to be a clone. ' +1340            'This is a bug in React.',1341        );1342      }1343    }1344    current.baseQueue = baseQueue = pendingQueue;1345    queue.pending = null;1346  }13471348  const baseState = hook.baseState;1349  if (baseQueue === null) {1350    // If there are no pending updates, then the memoized state should be the1351    // same as the base state. Currently these only diverge in the case of1352    // useOptimistic, because useOptimistic accepts a new baseState on1353    // every render.1354    hook.memoizedState = baseState;1355    // We don't need to call markWorkInProgressReceivedUpdate because1356    // baseState is derived from other reactive values.1357  } else {1358    // We have a queue to process.1359    const first = baseQueue.next;1360    let newState = baseState;13611362    let newBaseState = null;1363    let newBaseQueueFirst = null;1364    let newBaseQueueLast: Update<S, A> | null = null;1365    let update = first;1366    let didReadFromEntangledAsyncAction = false;1367    do {1368      // An extra OffscreenLane bit is added to updates that were made to1369      // a hidden tree, so that we can distinguish them from updates that were1370      // already there when the tree was hidden.1371      const updateLane = removeLanes(update.lane, OffscreenLane);1372      const isHiddenUpdate = updateLane !== update.lane;13731374      // Check if this update was made while the tree was hidden. If so, then1375      // it's not a "base" update and we should disregard the extra base lanes1376      // that were added to renderLanes when we entered the Offscreen tree.1377      let shouldSkipUpdate = isHiddenUpdate1378        ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)1379        : !isSubsetOfLanes(renderLanes, updateLane);13801381      if (enableGestureTransition && updateLane === GestureLane) {1382        // This is a gesture optimistic update. It should only be considered as part of the1383        // rendered state while rendering the gesture lane and if the rendering the associated1384        // ScheduledGesture.1385        const scheduledGesture = update.gesture;1386        if (scheduledGesture !== null) {1387          if (scheduledGesture.count === 0 && !scheduledGesture.committing) {1388            // This gesture has already been cancelled. We can clean up this update.1389            update = update.next;1390            continue;1391          } else if (!isGestureRender(renderLanes)) {1392            shouldSkipUpdate = true;1393          } else {1394            const root: FiberRoot | null = getWorkInProgressRoot();1395            if (root === null) {1396              throw new Error(1397                'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1398              );1399            }1400            // We assume that the currently rendering gesture is the one first in the queue.1401            shouldSkipUpdate = root.pendingGestures !== scheduledGesture;1402          }1403        }1404      }14051406      if (shouldSkipUpdate) {1407        // Priority is insufficient. Skip this update. If this is the first1408        // skipped update, the previous update/state is the new base1409        // update/state.1410        const clone: Update<S, A> = {1411          lane: updateLane,1412          revertLane: update.revertLane,1413          gesture: update.gesture,1414          action: update.action,1415          hasEagerState: update.hasEagerState,1416          eagerState: update.eagerState,1417          next: null as any,1418        };1419        if (newBaseQueueLast === null) {1420          newBaseQueueFirst = newBaseQueueLast = clone;1421          newBaseState = newState;1422        } else {1423          newBaseQueueLast = newBaseQueueLast.next = clone;1424        }1425        // Update the remaining priority in the queue.1426        // TODO: Don't need to accumulate this. Instead, we can remove1427        // renderLanes from the original lanes.1428        currentlyRenderingFiber.lanes = mergeLanes(1429          currentlyRenderingFiber.lanes,1430          updateLane,1431        );1432        markSkippedUpdateLanes(updateLane);1433      } else {1434        // This update does have sufficient priority.14351436        // Check if this is an optimistic update.1437        const revertLane = update.revertLane;1438        if (revertLane === NoLane) {1439          // This is not an optimistic update, and we're going to apply it now.1440          // But, if there were earlier updates that were skipped, we need to1441          // leave this update in the queue so it can be rebased later.1442          if (newBaseQueueLast !== null) {1443            const clone: Update<S, A> = {1444              // This update is going to be committed so we never want uncommit1445              // it. Using NoLane works because 0 is a subset of all bitmasks, so1446              // this will never be skipped by the check above.1447              lane: NoLane,1448              revertLane: NoLane,1449              gesture: null,1450              action: update.action,1451              hasEagerState: update.hasEagerState,1452              eagerState: update.eagerState,1453              next: null as any,1454            };1455            newBaseQueueLast = newBaseQueueLast.next = clone;1456          }14571458          // Check if this update is part of a pending async action. If so,1459          // we'll need to suspend until the action has finished, so that it's1460          // batched together with future updates in the same action.1461          if (updateLane === peekEntangledActionLane()) {1462            didReadFromEntangledAsyncAction = true;1463          }1464        } else {1465          // This is an optimistic update. If the "revert" priority is1466          // sufficient, don't apply the update. Otherwise, apply the update,1467          // but leave it in the queue so it can be either reverted or1468          // rebased in a subsequent render.1469          if (isSubsetOfLanes(renderLanes, revertLane)) {1470            // The transition that this optimistic update is associated with1471            // has finished. Pretend the update doesn't exist by skipping1472            // over it.1473            update = update.next;14741475            // Check if this update is part of a pending async action. If so,1476            // we'll need to suspend until the action has finished, so that it's1477            // batched together with future updates in the same action.1478            if (revertLane === peekEntangledActionLane()) {1479              didReadFromEntangledAsyncAction = true;1480            }1481            continue;1482          } else {1483            const clone: Update<S, A> = {1484              // Once we commit an optimistic update, we shouldn't uncommit it1485              // until the transition it is associated with has finished1486              // (represented by revertLane). Using NoLane here works because 01487              // is a subset of all bitmasks, so this will never be skipped by1488              // the check above.1489              lane: NoLane,1490              // Reuse the same revertLane so we know when the transition1491              // has finished.1492              revertLane: update.revertLane,1493              gesture: null, // If it commits, it's no longer a gesture update.1494              action: update.action,1495              hasEagerState: update.hasEagerState,1496              eagerState: update.eagerState,1497              next: null as any,1498            };1499            if (newBaseQueueLast === null) {1500              newBaseQueueFirst = newBaseQueueLast = clone;1501              newBaseState = newState;1502            } else {1503              newBaseQueueLast = newBaseQueueLast.next = clone;1504            }1505            // Update the remaining priority in the queue.1506            // TODO: Don't need to accumulate this. Instead, we can remove1507            // renderLanes from the original lanes.1508            currentlyRenderingFiber.lanes = mergeLanes(1509              currentlyRenderingFiber.lanes,1510              revertLane,1511            );1512            markSkippedUpdateLanes(revertLane);1513          }1514        }15151516        // Process this update.1517        const action = update.action;1518        if (shouldDoubleInvokeUserFnsInHooksDEV) {1519          reducer(newState, action);1520        }1521        if (update.hasEagerState) {1522          // If this update is a state update (not a reducer) and was processed eagerly,1523          // we can use the eagerly computed state1524          newState = update.eagerState as any as S;1525        } else {1526          newState = reducer(newState, action);1527        }1528      }1529      update = update.next;1530      // $FlowFixMe[invalid-compare]1531    } while (update !== null && update !== first);15321533    if (newBaseQueueLast === null) {1534      newBaseState = newState;1535    } else {1536      newBaseQueueLast.next = newBaseQueueFirst as any;1537    }15381539    // Mark that the fiber performed work, but only if the new state is1540    // different from the current state.1541    if (!is(newState, hook.memoizedState)) {1542      markWorkInProgressReceivedUpdate();15431544      // Check if this update is part of a pending async action. If so, we'll1545      // need to suspend until the action has finished, so that it's batched1546      // together with future updates in the same action.1547      // TODO: Once we support hooks inside useMemo (or an equivalent1548      // memoization boundary like Forget), hoist this logic so that it only1549      // suspends if the memo boundary produces a new value.1550      if (didReadFromEntangledAsyncAction) {1551        const entangledActionThenable = peekEntangledActionThenable();1552        if (entangledActionThenable !== null) {1553          // TODO: Instead of the throwing the thenable directly, throw a1554          // special object like `use` does so we can detect if it's captured1555          // by userspace.1556          throw entangledActionThenable;1557        }1558      }1559    }15601561    hook.memoizedState = newState;1562    hook.baseState = newBaseState;1563    hook.baseQueue = newBaseQueueLast;15641565    queue.lastRenderedState = newState;1566  }15671568  if (baseQueue === null) {1569    // `queue.lanes` is used for entangling transitions. We can set it back to1570    // zero once the queue is empty.1571    queue.lanes = NoLanes;1572  }15731574  const dispatch: Dispatch<A> = queue.dispatch as any;1575  return [hook.memoizedState, dispatch];1576}15771578function rerenderReducer<S, I, A>(1579  reducer: (S, A) => S,1580  initialArg: I,1581  init?: I => S,1582): [S, Dispatch<A>] {1583  const hook = updateWorkInProgressHook();1584  const queue = hook.queue;15851586  if (queue === null) {1587    throw new Error(1588      'Should have a queue. You are likely calling Hooks conditionally, ' +1589        'which is not allowed. (https://react.dev/link/invalid-hook-call)',1590    );1591  }15921593  queue.lastRenderedReducer = reducer;15941595  // This is a re-render. Apply the new render phase updates to the previous1596  // work-in-progress hook.1597  const dispatch: Dispatch<A> = queue.dispatch as any;1598  const lastRenderPhaseUpdate = queue.pending;1599  let newState = hook.memoizedState;1600  if (lastRenderPhaseUpdate !== null) {1601    // The queue doesn't persist past this render pass.1602    queue.pending = null;16031604    const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;1605    let update = firstRenderPhaseUpdate;1606    do {1607      // Process this render phase update. We don't have to check the1608      // priority because it will always be the same as the current1609      // render's.1610      const action = update.action;1611      newState = reducer(newState, action);1612      update = update.next;1613    } while (update !== firstRenderPhaseUpdate);16141615    // Mark that the fiber performed work, but only if the new state is1616    // different from the current state.1617    if (!is(newState, hook.memoizedState)) {1618      markWorkInProgressReceivedUpdate();1619    }16201621    hook.memoizedState = newState;1622    // Don't persist the state accumulated from the render phase updates to1623    // the base state unless the queue is empty.1624    // TODO: Not sure if this is the desired semantics, but it's what we1625    // do for gDSFP. I can't remember why.1626    if (hook.baseQueue === null) {1627      hook.baseState = newState;1628    }16291630    queue.lastRenderedState = newState;1631  }1632  return [newState, dispatch];1633}16341635function mountSyncExternalStore<T>(1636  subscribe: (() => void) => () => void,1637  getSnapshot: () => T,1638  getServerSnapshot?: () => T,1639): T {1640  const fiber = currentlyRenderingFiber;1641  const hook = mountWorkInProgressHook();16421643  let nextSnapshot;1644  const isHydrating = getIsHydrating();1645  if (isHydrating) {1646    if (getServerSnapshot === undefined) {1647      throw new Error(1648        'Missing getServerSnapshot, which is required for ' +1649          'server-rendered content. Will revert to client rendering.',1650      );1651    }1652    nextSnapshot = getServerSnapshot();1653    if (__DEV__) {1654      if (!didWarnUncachedGetSnapshot) {1655        if (nextSnapshot !== getServerSnapshot()) {1656          console.error(1657            'The result of getServerSnapshot should be cached to avoid an infinite loop',1658          );1659          didWarnUncachedGetSnapshot = true;1660        }1661      }1662    }1663  } else {1664    nextSnapshot = getSnapshot();1665    if (__DEV__) {1666      if (!didWarnUncachedGetSnapshot) {1667        const cachedSnapshot = getSnapshot();1668        if (!is(nextSnapshot, cachedSnapshot)) {1669          console.error(1670            'The result of getSnapshot should be cached to avoid an infinite loop',1671          );1672          didWarnUncachedGetSnapshot = true;1673        }1674      }1675    }1676    // Unless we're rendering a blocking lane, schedule a consistency check.1677    // Right before committing, we will walk the tree and check if any of the1678    // stores were mutated.1679    //1680    // We won't do this if we're hydrating server-rendered content, because if1681    // the content is stale, it's already visible anyway. Instead we'll patch1682    // it up in a passive effect.1683    const root: FiberRoot | null = getWorkInProgressRoot();16841685    if (root === null) {1686      throw new Error(1687        'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1688      );1689    }16901691    const rootRenderLanes = getWorkInProgressRootRenderLanes();1692    if (!includesBlockingLane(rootRenderLanes)) {1693      pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);1694    }1695  }16961697  // Read the current snapshot from the store on every render. This breaks the1698  // normal rules of React, and only works because store updates are1699  // always synchronous.1700  hook.memoizedState = nextSnapshot;1701  const inst: StoreInstance<T> = {1702    value: nextSnapshot,1703    getSnapshot,1704  };1705  hook.queue = inst;17061707  // Schedule an effect to subscribe to the store.1708  mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);17091710  // Schedule an effect to update the mutable instance fields. We will update1711  // this whenever subscribe, getSnapshot, or value changes. Because there's no1712  // clean-up function, and we track the deps correctly, we can call pushEffect1713  // directly, without storing any additional state. For the same reason, we1714  // don't need to set a static flag, either.1715  fiber.flags |= PassiveEffect;1716  pushSimpleEffect(1717    HookHasEffect | HookPassive,1718    createEffectInstance(),1719    updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),1720    null,1721  );17221723  return nextSnapshot;1724}17251726function updateSyncExternalStore<T>(1727  subscribe: (() => void) => () => void,1728  getSnapshot: () => T,1729  getServerSnapshot?: () => T,1730): T {1731  const fiber = currentlyRenderingFiber;1732  const hook = updateWorkInProgressHook();1733  // Read the current snapshot from the store on every render. This breaks the1734  // normal rules of React, and only works because store updates are1735  // always synchronous.1736  let nextSnapshot;1737  const isHydrating = getIsHydrating();1738  if (isHydrating) {1739    // Needed for strict mode double render1740    if (getServerSnapshot === undefined) {1741      throw new Error(1742        'Missing getServerSnapshot, which is required for ' +1743          'server-rendered content. Will revert to client rendering.',1744      );1745    }1746    nextSnapshot = getServerSnapshot();1747  } else {1748    nextSnapshot = getSnapshot();1749    if (__DEV__) {1750      if (!didWarnUncachedGetSnapshot) {1751        const cachedSnapshot = getSnapshot();1752        if (!is(nextSnapshot, cachedSnapshot)) {1753          console.error(1754            'The result of getSnapshot should be cached to avoid an infinite loop',1755          );1756          didWarnUncachedGetSnapshot = true;1757        }1758      }1759    }1760  }1761  const prevSnapshot = (currentHook || hook).memoizedState;1762  const snapshotChanged = !is(prevSnapshot, nextSnapshot);1763  if (snapshotChanged) {1764    hook.memoizedState = nextSnapshot;1765    markWorkInProgressReceivedUpdate();1766  }1767  const inst = hook.queue;17681769  updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [1770    subscribe,1771  ]);17721773  // Whenever getSnapshot or subscribe changes, we need to check in the1774  // commit phase if there was an interleaved mutation. In concurrent mode1775  // this can happen all the time, but even in synchronous mode, an earlier1776  // effect may have mutated the store.1777  if (1778    inst.getSnapshot !== getSnapshot ||1779    snapshotChanged ||1780    // Check if the subscribe function changed. We can save some memory by1781    // checking whether we scheduled a subscription effect above.1782    (workInProgressHook !== null &&1783      workInProgressHook.memoizedState.tag & HookHasEffect)1784  ) {1785    fiber.flags |= PassiveEffect;1786    pushSimpleEffect(1787      HookHasEffect | HookPassive,1788      createEffectInstance(),1789      updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),1790      null,1791    );17921793    // Unless we're rendering a blocking lane, schedule a consistency check.1794    // Right before committing, we will walk the tree and check if any of the1795    // stores were mutated.1796    const root: FiberRoot | null = getWorkInProgressRoot();17971798    if (root === null) {1799      throw new Error(1800        'Expected a work-in-progress root. This is a bug in React. Please file an issue.',1801      );1802    }18031804    if (!isHydrating && !includesBlockingLane(renderLanes)) {1805      pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);1806    }1807  }18081809  return nextSnapshot;1810}18111812function pushStoreConsistencyCheck<T>(1813  fiber: Fiber,1814  getSnapshot: () => T,1815  renderedSnapshot: T,1816): void {1817  fiber.flags |= StoreConsistency;1818  const check: StoreConsistencyCheck<T> = {1819    getSnapshot,1820    value: renderedSnapshot,1821  };1822  let componentUpdateQueue: null | FunctionComponentUpdateQueue =1823    currentlyRenderingFiber.updateQueue as any;1824  if (componentUpdateQueue === null) {1825    componentUpdateQueue = createFunctionComponentUpdateQueue();1826    currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;1827    componentUpdateQueue.stores = [check];1828  } else {1829    const stores = componentUpdateQueue.stores;1830    if (stores === null) {1831      componentUpdateQueue.stores = [check];1832    } else {1833      stores.push(check);1834    }1835  }1836}18371838function updateStoreInstance<T>(1839  fiber: Fiber,1840  inst: StoreInstance<T>,1841  nextSnapshot: T,1842  getSnapshot: () => T,1843): void {1844  // These are updated in the passive phase1845  inst.value = nextSnapshot;1846  inst.getSnapshot = getSnapshot;18471848  // Something may have been mutated in between render and commit. This could1849  // have been in an event that fired before the passive effects, or it could1850  // have been in a layout effect. In that case, we would have used the old1851  // snapsho and getSnapshot values to bail out. We need to check one more time.1852  if (checkIfSnapshotChanged(inst)) {1853    // Force a re-render.1854    // We intentionally don't log update times and stacks here because this1855    // was not an external trigger but rather an internal one.1856    forceStoreRerender(fiber);1857  }1858}18591860function subscribeToStore<T>(1861  fiber: Fiber,1862  inst: StoreInstance<T>,1863  subscribe: (() => void) => () => void,1864): any {1865  const handleStoreChange = () => {1866    // The store changed. Check if the snapshot changed since the last time we1867    // read from the store.1868    if (checkIfSnapshotChanged(inst)) {1869      // Force a re-render.1870      startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber);1871      forceStoreRerender(fiber);1872    }1873  };1874  // Subscribe to the store and return a clean-up function.1875  return subscribe(handleStoreChange);1876}18771878function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {1879  const latestGetSnapshot = inst.getSnapshot;1880  const prevValue = inst.value;1881  try {1882    const nextValue = latestGetSnapshot();1883    return !is(prevValue, nextValue);1884  } catch (error) {1885    return true;1886  }1887}18881889function forceStoreRerender(fiber: Fiber) {1890  const root = enqueueConcurrentRenderForLane(fiber, SyncLane);1891  if (root !== null) {1892    scheduleUpdateOnFiber(root, fiber, SyncLane);1893  }1894}18951896function mountStateImpl<S>(initialState: (() => S) | S): Hook {1897  const hook = mountWorkInProgressHook();1898  if (typeof initialState === 'function') {1899    const initialStateInitializer = initialState;1900    // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1901    initialState = initialStateInitializer();1902    if (shouldDoubleInvokeUserFnsInHooksDEV) {1903      setIsStrictModeForDevtools(true);1904      try {1905        // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types1906        initialStateInitializer();1907      } finally {1908        setIsStrictModeForDevtools(false);1909      }1910    }1911  }1912  hook.memoizedState = hook.baseState = initialState;1913  const queue: UpdateQueue<S, BasicStateAction<S>> = {1914    pending: null,1915    lanes: NoLanes,1916    dispatch: null,1917    lastRenderedReducer: basicStateReducer,1918    lastRenderedState: initialState as any,1919  };1920  hook.queue = queue;1921  return hook;1922}19231924function mountState<S>(1925  initialState: (() => S) | S,1926): [S, Dispatch<BasicStateAction<S>>] {1927  const hook = mountStateImpl(initialState);1928  const queue = hook.queue;1929  const dispatch: Dispatch<BasicStateAction<S>> = dispatchSetState.bind(1930    null,1931    currentlyRenderingFiber,1932    queue,1933  ) as any;1934  queue.dispatch = dispatch;1935  return [hook.memoizedState, dispatch];1936}19371938function updateState<S>(1939  initialState: (() => S) | S,1940): [S, Dispatch<BasicStateAction<S>>] {1941  return updateReducer(basicStateReducer, initialState);1942}19431944function rerenderState<S>(1945  initialState: (() => S) | S,1946): [S, Dispatch<BasicStateAction<S>>] {1947  return rerenderReducer(basicStateReducer, initialState);1948}19491950function mountOptimistic<S, A>(1951  passthrough: S,1952  reducer: ?(S, A) => S,1953): [S, (A) => void] {1954  const hook = mountWorkInProgressHook();1955  hook.memoizedState = hook.baseState = passthrough;1956  const queue: UpdateQueue<S, A> = {1957    pending: null,1958    lanes: NoLanes,1959    dispatch: null,1960    // Optimistic state does not use the eager update optimization.1961    lastRenderedReducer: null,1962    lastRenderedState: null,1963  };1964  hook.queue = queue;1965  // This is different than the normal setState function.1966  const dispatch: A => void = dispatchOptimisticSetState.bind(1967    null,1968    currentlyRenderingFiber,1969    true,1970    queue,1971  ) as any;1972  queue.dispatch = dispatch;1973  return [passthrough, dispatch];1974}19751976function updateOptimistic<S, A>(1977  passthrough: S,1978  reducer: ?(S, A) => S,1979): [S, (A) => void] {1980  const hook = updateWorkInProgressHook();1981  return updateOptimisticImpl(1982    hook,1983    currentHook as any as Hook,1984    passthrough,1985    reducer,1986  );1987}19881989function updateOptimisticImpl<S, A>(1990  hook: Hook,1991  current: Hook | null,1992  passthrough: S,1993  reducer: ?(S, A) => S,1994): [S, (A) => void] {1995  // Optimistic updates are always rebased on top of the latest value passed in1996  // as an argument. It's called a passthrough because if there are no pending1997  // updates, it will be returned as-is.1998  //1999  // Reset the base state to the passthrough. Future updates will be applied2000  // on top of this.

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.