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.