/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
ReactContext,
StartTransitionOptions,
Usable,
Thenable,
RejectedThenable,
Awaited,
} from 'shared/ReactTypes';
import type {
Fiber,
FiberRoot,
Dispatcher,
HookType,
MemoCache,
} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane';
import type {HookFlags} from './ReactHookEffectTags';
import type {Flags} from './ReactFiberFlags';
import type {TransitionStatus} from './ReactFiberConfig';
import {NotPendingTransition as NoPendingHostTransition} from './ReactFiberConfig';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableDebugTracing,
enableSchedulingProfiler,
enableCache,
enableUseRefAccessWarning,
enableLazyContextPropagation,
enableTransitionTracing,
enableUseMemoCacheHook,
enableUseEffectEventHook,
enableLegacyCache,
debugRenderPhaseSideEffectsForStrictMode,
enableAsyncActions,
enableFormActions,
enableUseDeferredValueInitialArg,
} from 'shared/ReactFeatureFlags';
import {
REACT_CONTEXT_TYPE,
REACT_SERVER_CONTEXT_TYPE,
REACT_MEMO_CACHE_SENTINEL,
} from 'shared/ReactSymbols';
import {
NoMode,
ConcurrentMode,
DebugTracingMode,
StrictEffectsMode,
StrictLegacyMode,
NoStrictPassiveEffectsMode,
} from './ReactTypeOfMode';
import {
NoLane,
SyncLane,
OffscreenLane,
DeferredLane,
NoLanes,
isSubsetOfLanes,
includesBlockingLane,
includesOnlyNonUrgentLanes,
mergeLanes,
removeLanes,
intersectLanes,
isTransitionLane,
markRootEntangled,
includesSomeLane,
} from './ReactFiberLane';
import {
ContinuousEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
higherEventPriority,
} from './ReactEventPriorities';
import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
import {
LayoutStatic as LayoutStaticEffect,
Passive as PassiveEffect,
PassiveStatic as PassiveStaticEffect,
StaticMask as StaticMaskEffect,
Update as UpdateEffect,
StoreConsistency,
MountLayoutDev as MountLayoutDevEffect,
MountPassiveDev as MountPassiveDevEffect,
} from './ReactFiberFlags';
import {
HasEffect as HookHasEffect,
Layout as HookLayout,
Passive as HookPassive,
Insertion as HookInsertion,
} from './ReactHookEffectTags';
import {
getWorkInProgressRoot,
getWorkInProgressRootRenderLanes,
scheduleUpdateOnFiber,
requestUpdateLane,
requestDeferredLane,
markSkippedUpdateLanes,
isInvalidExecutionContextForEventFunction,
} from './ReactFiberWorkLoop';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import is from 'shared/objectIs';
import isArray from 'shared/isArray';
import {
markWorkInProgressReceivedUpdate,
checkIfWorkInProgressReceivedUpdate,
} from './ReactFiberBeginWork';
import {
getIsHydrating,
tryToClaimNextHydratableFormMarkerInstance,
} from './ReactFiberHydrationContext';
import {logStateUpdateScheduled} from './DebugTracing';
import {
markStateUpdateScheduled,
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook';
import {createCache} from './ReactFiberCacheComponent';
import {
createUpdate as createLegacyQueueUpdate,
enqueueUpdate as enqueueLegacyQueueUpdate,
entangleTransitions as entangleLegacyQueueTransitions,
} from './ReactFiberClassUpdateQueue';
import {
enqueueConcurrentHookUpdate,
enqueueConcurrentHookUpdateAndEagerlyBailout,
enqueueConcurrentRenderForLane,
} from './ReactFiberConcurrentUpdates';
import {getTreeId} from './ReactFiberTreeContext';
import {now} from './Scheduler';
import {
trackUsedThenable,
checkIfUseWrappedInTryCatch,
createThenableState,
} from './ReactFiberThenable';
import type {ThenableState} from './ReactFiberThenable';
import type {BatchConfigTransition} from './ReactFiberTracingMarkerComponent';
import {
requestAsyncActionContext,
requestSyncActionContext,
peekEntangledActionLane,
} from './ReactFiberAsyncAction';
import {HostTransitionContext} from './ReactFiberHostContext';
import {requestTransitionLane} from './ReactFiberRootScheduler';
import {isCurrentTreeHidden} from './ReactFiberHiddenContext';
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
export type Update<S, A> = {
lane: Lane,
revertLane: Lane,
action: A,
hasEagerState: boolean,
eagerState: S | null,
next: Update<S, A>,
};
export type UpdateQueue<S, A> = {
pending: Update<S, A> | null,
lanes: Lanes,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
};
let didWarnAboutMismatchedHooksForComponent;
let didWarnUncachedGetSnapshot: void | true;
let didWarnAboutUseWrappedInTryCatch;
let didWarnAboutAsyncClientComponent;
if (__DEV__) {
didWarnAboutMismatchedHooksForComponent = new Set<string | null>();
didWarnAboutUseWrappedInTryCatch = new Set<string | null>();
didWarnAboutAsyncClientComponent = new Set<string | null>();
}
export type Hook = {
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: any,
next: Hook | null,
};
// The effect "instance" is a shared object that remains the same for the entire
// lifetime of an effect. In Rust terms, a RefCell. We use it to store the
// "destroy" function that is returned from an effect, because that is stateful.
// The field is `undefined` if the effect is unmounted, or if the effect ran
// but is not stateful. We don't explicitly track whether the effect is mounted
// or unmounted because that can be inferred by the hiddenness of the fiber in
// the tree, i.e. whether there is a hidden Offscreen fiber above it.
//
// It's unfortunate that this is stored on a separate object, because it adds
// more memory per effect instance, but it's conceptually sound. I think there's
// likely a better data structure we could use for effects; perhaps just one
// array of effect instances per fiber. But I think this is OK for now despite
// the additional memory and we can follow up with performance
// optimizations later.
type EffectInstance = {
destroy: void | (() => void),
};
export type Effect = {
tag: HookFlags,
create: () => (() => void) | void,
inst: EffectInstance,
deps: Array<mixed> | null,
next: Effect,
};
type StoreInstance<T> = {
value: T,
getSnapshot: () => T,
};
type StoreConsistencyCheck<T> = {
value: T,
getSnapshot: () => T,
};
type EventFunctionPayload<Args, Return, F: (...Array<Args>) => Return> = {
ref: {
eventFn: F,
impl: F,
},
nextImpl: F,
};
export type FunctionComponentUpdateQueue = {
lastEffect: Effect | null,
events: Array<EventFunctionPayload<any, any, any>> | null,
stores: Array<StoreConsistencyCheck<any>> | null,
// NOTE: optional, only set when enableUseMemoCacheHook is enabled
memoCache?: MemoCache | null,
};
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
// These are set right before calling the component.
let renderLanes: Lanes = NoLanes;
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber = (null: any);
// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
// Whether an update was scheduled at any point during the render phase. This
// does not get reset if we do another render pass; only when we're completely
// finished evaluating this component. This is an optimization so we know
// whether we need to clear render phase updates after a throw.
let didScheduleRenderPhaseUpdate: boolean = false;
// Where an update was scheduled only during the current render pass. This
// gets reset after each attempt.
// TODO: Maybe there's some way to consolidate this with
// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
let shouldDoubleInvokeUserFnsInHooksDEV: boolean = false;
// Counts the number of useId hooks in this component.
let localIdCounter: number = 0;
// Counts number of `use`-d thenables
let thenableIndexCounter: number = 0;
let thenableState: ThenableState | null = null;
// Used for ids that are generated completely client-side (i.e. not during
// hydration). This counter is global, so client ids are not stable across
// render attempts.
let globalClientIdCounter: number = 0;
const RE_RENDER_LIMIT = 25;
// In DEV, this is the name of the currently executing primitive hook
let currentHookNameInDev: ?HookType = null;
// In DEV, this list ensures that hooks are called in the same order between renders.
// The list stores the order of hooks used during the initial render (mount).
// Subsequent renders (updates) reference this list.
let hookTypesDev: Array<HookType> | null = null;
let hookTypesUpdateIndexDev: number = -1;
// In DEV, this tracks whether currently rendering component needs to ignore
// the dependencies for Hooks that need them (e.g. useEffect or useMemo).
// When true, such Hooks will always be "remounted". Only used during hot reload.
let ignorePreviousDependencies: boolean = false;
function mountHookTypesDev(): void {
if (__DEV__) {
const hookName = ((currentHookNameInDev: any): HookType);
if (hookTypesDev === null) {
hookTypesDev = [hookName];
} else {
hookTypesDev.push(hookName);
}
}
}
function updateHookTypesDev(): void {
if (__DEV__) {
const hookName = ((currentHookNameInDev: any): HookType);
if (hookTypesDev !== null) {
hookTypesUpdateIndexDev++;
if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
warnOnHookMismatchInDev(hookName);
}
}
}
}
function checkDepsAreArrayDev(deps: mixed): void {
if (__DEV__) {
if (deps !== undefined && deps !== null && !isArray(deps)) {
// Verify deps, but only on mount to avoid extra checks.
// It's unlikely their type would change as usually you define them inline.
console.error(
'%s received a final argument that is not an array (instead, received `%s`). When ' +
'specified, the final argument must be an array.',
currentHookNameInDev,
typeof deps,
);
}
}
}
function warnOnHookMismatchInDev(currentHookName: HookType): void {
if (__DEV__) {
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
didWarnAboutMismatchedHooksForComponent.add(componentName);
if (hookTypesDev !== null) {
let table = '';
const secondColumnStart = 30;
for (let i = 0; i <= ((hookTypesUpdateIndexDev: any): number); i++) {
const oldHookName = hookTypesDev[i];
const newHookName =
i === ((hookTypesUpdateIndexDev: any): number)
? currentHookName
: oldHookName;
let row = `${i + 1}. ${oldHookName}`;
// Extra space so second column lines up
// lol @ IE not supporting String#repeat
while (row.length < secondColumnStart) {
row += ' ';
}
row += newHookName + '\n';
table += row;
}
console.error(
'React has detected a change in the order of Hooks called by %s. ' +
'This will lead to bugs and errors if not fixed. ' +
'For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks\n\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'%s' +
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
componentName,
table,
);
}
}
}
}
function warnIfAsyncClientComponent(
Component: Function,
componentDoesIncludeHooks: boolean,
) {
if (__DEV__) {
// This dev-only check only works for detecting native async functions,
// not transpiled ones. There's also a prod check that we use to prevent
// async client components from crashing the app; the prod one works even
// for transpiled async functions. Neither mechanism is completely
// bulletproof but together they cover the most common cases.
const isAsyncFunction =
// $FlowIgnore[method-unbinding]
Object.prototype.toString.call(Component) === '[object AsyncFunction]';
if (isAsyncFunction) {
// Encountered an async Client Component. This is not yet supported,
// except in certain constrained cases, like during a route navigation.
const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
if (!didWarnAboutAsyncClientComponent.has(componentName)) {
didWarnAboutAsyncClientComponent.add(componentName);
// Check if this is a sync update. We use the "root" render lanes here
// because the "subtree" render lanes may include additional entangled
// lanes related to revealing previously hidden content.
const root = getWorkInProgressRoot();
const rootRenderLanes = getWorkInProgressRootRenderLanes();
if (root !== null && includesBlockingLane(root, rootRenderLanes)) {
console.error(
'async/await is not yet supported in Client Components, only ' +
'Server Components. This error is often caused by accidentally ' +
"adding `'use client'` to a module that was originally written " +
'for the server.',
);
} else {
// This is a concurrent (Transition, Retry, etc) render. We don't
// warn in these cases.
//
// However, Async Components are forbidden to include hooks, even
// during a transition, so let's check for that here.
//
// TODO: Add a corresponding warning to Server Components runtime.
if (componentDoesIncludeHooks) {
console.error(
'Hooks are not supported inside an async component. This ' +
"error is often caused by accidentally adding `'use client'` " +
'to a module that was originally written for the server.',
);
}
}
}
}
}
}
function throwInvalidHookError() {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
}
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
if (__DEV__) {
if (ignorePreviousDependencies) {
// Only true when this component is being hot reloaded.
return false;
}
}
if (prevDeps === null) {
if (__DEV__) {
console.error(
'%s received a final argument during this render, but not during ' +
'the previous render. Even though the final argument is optional, ' +
'its type cannot change between renders.',
currentHookNameInDev,
);
}
return false;
}
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
if (nextDeps.length !== prevDeps.length) {
console.error(
'The final argument passed to %s changed size between renders. The ' +
'order and size of this array must remain constant.\n\n' +
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${prevDeps.join(', ')}]`,
`[${nextDeps.join(', ')}]`,
);
}
}
// $FlowFixMe[incompatible-use] found when upgrading Flow
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
if (__DEV__) {
hookTypesDev =
current !== null
? ((current._debugHookTypes: any): Array<HookType>)
: null;
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
}
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// didScheduleRenderPhaseUpdate = false;
// localIdCounter = 0;
// thenableIndexCounter = 0;
// thenableState = null;
// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because memoizedState === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so memoizedState would be null during updates and mounts.
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
// This dispatcher handles an edge case where a component is updating,
// but no stateful hooks have been used.
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
// This dispatcher does that.
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
// In Strict Mode, during development, user functions are double invoked to
// help detect side effects. The logic for how this is implemented for in
// hook components is a bit complex so let's break it down.
//
// We will invoke the entire component function twice. However, during the
// second invocation of the component, the hook state from the first
// invocation will be reused. That means things like `useMemo` functions won't
// run again, because the deps will match and the memoized result will
// be reused.
//
// We want memoized functions to run twice, too, so account for this, user
// functions are double invoked during the *first* invocation of the component
// function, and are *not* double invoked during the second incovation:
//
// - First execution of component function: user functions are double invoked
// - Second execution of component function (in Strict Mode, during
// development): user functions are not double invoked.
//
// This is intentional for a few reasons; most importantly, it's because of
// how `use` works when something suspends: it reuses the promise that was
// passed during the first attempt. This is itself a form of memoization.
// We need to be able to memoize the reactive inputs to the `use` call using
// a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must
// come from the same component invocation as the output.
//
// There are plenty of tests to ensure this behavior is correct.
const shouldDoubleRenderDEV =
__DEV__ &&
debugRenderPhaseSideEffectsForStrictMode &&
(workInProgress.mode & StrictLegacyMode) !== NoMode;
shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;
let children = Component(props, secondArg);
shouldDoubleInvokeUserFnsInHooksDEV = false;
// Check if there was a render phase update
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering until the component stabilizes (there are no more render
// phase updates).
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
}
if (shouldDoubleRenderDEV) {
// In development, components are invoked twice to help detect side effects.
setIsStrictModeForDevtools(true);
try {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
} finally {
setIsStrictModeForDevtools(false);
}
}
finishRenderingHooks(current, workInProgress, Component);
return children;
}
function finishRenderingHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
): void {
if (__DEV__) {
workInProgress._debugHookTypes = hookTypesDev;
const componentDoesIncludeHooks =
workInProgressHook !== null || thenableIndexCounter !== 0;
warnIfAsyncClientComponent(Component, componentDoesIncludeHooks);
}
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrance.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
if (__DEV__) {
currentHookNameInDev = null;
hookTypesDev = null;
hookTypesUpdateIndexDev = -1;
// Confirm that a static flag was not added or removed since the last
// render. If this fires, it suggests that we incorrectly reset the static
// flags in some other part of the codebase. This has happened before, for
// example, in the SuspenseList implementation.
if (
current !== null &&
(current.flags & StaticMaskEffect) !==
(workInProgress.flags & StaticMaskEffect) &&
// Disable this warning in legacy mode, because legacy Suspense is weird
// and creates false positives. To make this work in legacy mode, we'd
// need to mark fibers that commit in an incomplete state, somehow. For
// now I'll disable the warning that most of the bugs that would trigger
// it are either exclusive to concurrent mode or exist in both.
(current.mode & ConcurrentMode) !== NoMode
) {
console.error(
'Internal React error: Expected static flag was missing. Please ' +
'notify the React team.',
);
}
}
didScheduleRenderPhaseUpdate = false;
// This is reset by checkDidRenderIdHook
// localIdCounter = 0;
thenableIndexCounter = 0;
thenableState = null;
if (didRenderTooFewHooks) {
throw new Error(
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);
}
if (enableLazyContextPropagation) {
if (current !== null) {
if (!checkIfWorkInProgressReceivedUpdate()) {
// If there were no changes to props or state, we need to check if there
// was a context change. We didn't already do this because there's no
// 1:1 correspondence between dependencies and hooks. Although, because
// there almost always is in the common case (`readContext` is an
// internal API), we could compare in there. OTOH, we only hit this case
// if everything else bails out, so on the whole it might be better to
// keep the comparison out of the common path.
const currentDependencies = current.dependencies;
if (
currentDependencies !== null &&
checkIfContextChanged(currentDependencies)
) {
markWorkInProgressReceivedUpdate();
}
}
}
}
if (__DEV__) {
if (checkIfUseWrappedInTryCatch()) {
const componentName =
getComponentNameFromFiber(workInProgress) || 'Unknown';
if (
!didWarnAboutUseWrappedInTryCatch.has(componentName) &&
// This warning also fires if you suspend with `use` inside an
// async component. Since we warn for that above, we'll silence this
// second warning by checking here.
!didWarnAboutAsyncClientComponent.has(componentName)
) {
didWarnAboutUseWrappedInTryCatch.add(componentName);
console.error(
'`use` was called from inside a try/catch block. This is not allowed ' +
'and can lead to unexpected behavior. To handle errors triggered ' +
'by `use`, wrap your component in a error boundary.',
);
}
}
}
}
export function replaySuspendedComponentWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
// This function is used to replay a component that previously suspended,
// after its data resolves.
//
// It's a simplified version of renderWithHooks, but it doesn't need to do
// most of the set up work because they weren't reset when we suspended; they
// only get reset when the component either completes (finishRenderingHooks)
// or unwinds (resetHooksOnUnwind).
if (__DEV__) {
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
}
const children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg,
);
finishRenderingHooks(current, workInProgress, Component);
return children;
}
function renderWithHooksAgain<Props, SecondArg>(
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
// This is used to perform another render pass. It's used when setState is
// called during render, and for double invoking components in Strict Mode
// during development.
//
// The state from the previous pass is reused whenever possible. So, state
// updates that were already processed are not processed again, and memoized
// functions (`useMemo`) are not invoked again.
//
// Keep rendering in a loop for as long as render phase updates continue to
// be scheduled. Use a counter to prevent infinite loops.
currentlyRenderingFiber = workInProgress;
let numberOfReRenders: number = 0;
let children;
do {
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// It's possible that a use() value depended on a state that was updated in
// this rerender, so we need to watch for different thenables this time.
thenableState = null;
}
thenableIndexCounter = 0;
didScheduleRenderPhaseUpdateDuringThisPass = false;
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error(
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
}
numberOfReRenders += 1;
if (__DEV__) {
// Even when hot reloading, allow dependencies to stabilize
// after first render to prevent infinite render phase updates.
ignorePreviousDependencies = false;
}
// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
if (__DEV__) {
// Also validate hook order for cascading updates.
hookTypesUpdateIndexDev = -1;
}
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
return children;
}
export function renderTransitionAwareHostComponentWithHooks(
current: Fiber | null,
workInProgress: Fiber,
lanes: Lanes,
): TransitionStatus {
if (!(enableFormActions && enableAsyncActions)) {
throw new Error('Not implemented.');
}
return renderWithHooks(
current,
workInProgress,
TransitionAwareHostComponent,
null,
null,
lanes,
);
}
export function TransitionAwareHostComponent(): TransitionStatus {
if (!(enableFormActions && enableAsyncActions)) {
throw new Error('Not implemented.');
}
const dispatcher = ReactCurrentDispatcher.current;
const [maybeThenable] = dispatcher.useState();
if (typeof maybeThenable.then === 'function') {
const thenable: Thenable<TransitionStatus> = (maybeThenable: any);
return useThenable(thenable);
} else {
const status: TransitionStatus = maybeThenable;
return status;
}
}
export function checkDidRenderIdHook(): boolean {
// This should be called immediately after every renderWithHooks call.
// Conceptually, it's part of the return value of renderWithHooks; it's only a
// separate function to avoid using an array tuple.
const didRenderIdHook = localIdCounter !== 0;
localIdCounter = 0;
return didRenderIdHook;
}
export function bailoutHooks(
current: Fiber,
workInProgress: Fiber,
lanes: Lanes,
): void {
workInProgress.updateQueue = current.updateQueue;
// TODO: Don't need to reset the flags here, because they're reset in the
// complete phase (bubbleProperties).
if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
workInProgress.flags &= ~(
MountPassiveDevEffect |
MountLayoutDevEffect |
PassiveEffect |
UpdateEffect
);
} else {
workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
}
current.lanes = removeLanes(current.lanes, lanes);
}
export function resetHooksAfterThrow(): void {
// This is called immediaetly after a throw. It shouldn't reset the entire
// module state, because the work loop might decide to replay the component
// again without rewinding.
//
// It should only reset things like the current dispatcher, to prevent hooks
// from being called outside of a component.
currentlyRenderingFiber = (null: any);
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrance.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
}
export function resetHooksOnUnwind(workInProgress: Fiber): void {
if (didScheduleRenderPhaseUpdate) {
// There were render phase updates. These are only valid for this render
// phase, which we are now aborting. Remove the updates from the queues so
// they do not persist to the next render. Do not remove updates from hooks
// that weren't processed.
//
// Only reset the updates from the queue if it has a clone. If it does
// not have a clone, that means it wasn't processed, and the updates were
// scheduled before we entered the render phase.
let hook: Hook | null = workInProgress.memoizedState;
while (hook !== null) {
const queue = hook.queue;
if (queue !== null) {
queue.pending = null;
}
hook = hook.next;
}
didScheduleRenderPhaseUpdate = false;
}
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
if (__DEV__) {
hookTypesDev = null;
hookTypesUpdateIndexDev = -1;
currentHookNameInDev = null;
}
didScheduleRenderPhaseUpdateDuringThisPass = false;
localIdCounter = 0;
thenableIndexCounter = 0;
thenableState = null;
}
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function updateWorkInProgressHook(): Hook {
// This function is used both for updates and for re-renders triggered by a
// render phase update. It assumes there is either a current hook we can
// clone, or a work-in-progress hook from a previous render pass that we can
// use as a base.
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
if (nextCurrentHook === null) {
const currentFiber = currentlyRenderingFiber.alternate;
if (currentFiber === null) {
// This is the initial render. This branch is reached when the component
// suspends, resumes, then renders an additional hook.
// Should never be reached because we should switch to the mount dispatcher first.
throw new Error(
'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
);
} else {
// This is an update. We should always have a current hook.
throw new Error('Rendered more hooks than during the previous render.');
}
}
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list.
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
// NOTE: defining two versions of this function to avoid size impact when this feature is disabled.
// Previously this function was inlined, the additional `memoCache` property makes it not inlined.
let createFunctionComponentUpdateQueue: () => FunctionComponentUpdateQueue;
if (enableUseMemoCacheHook) {
createFunctionComponentUpdateQueue = () => {
return {
lastEffect: null,
events: null,
stores: null,
memoCache: null,
};
};
} else {
createFunctionComponentUpdateQueue = () => {
return {
lastEffect: null,
events: null,
stores: null,
};
};
}
function useThenable<T>(thenable: Thenable<T>): T {
// Track the position of the thenable within this fiber.
const index = thenableIndexCounter;
thenableIndexCounter += 1;
if (thenableState === null) {
thenableState = createThenableState();
}
const result = trackUsedThenable(thenableState, thenable, index);
if (
currentlyRenderingFiber.alternate === null &&
(workInProgressHook === null
? currentlyRenderingFiber.memoizedState === null
: workInProgressHook.next === null)
) {
// Initial render, and either this is the first time the component is
// called, or there were no Hooks called after this use() the previous
// time (perhaps because it threw). Subsequent Hook calls should use the
// mount dispatcher.
if (__DEV__) {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
}
return result;
}
function use<T>(usable: Usable<T>): T {
if (usable !== null && typeof usable === 'object') {
// $FlowFixMe[method-unbinding]
if (typeof usable.then === 'function') {
// This is a thenable.
const thenable: Thenable<T> = (usable: any);
return useThenable(thenable);
} else if (
usable.$$typeof === REACT_CONTEXT_TYPE ||
usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
) {
const context: ReactContext<T> = (usable: any);
return readContext(context);
}
}
// eslint-disable-next-line react-internal/safe-string-coercion
throw new Error('An unsupported type was passed to use(): ' + String(usable));
}
function useMemoCache(size: number): Array<any> {
let memoCache = null;
// Fast-path, load memo cache from wip fiber if already prepared
let updateQueue: FunctionComponentUpdateQueue | null =
(currentlyRenderingFiber.updateQueue: any);
if (updateQueue !== null) {
memoCache = updateQueue.memoCache;
}
// Otherwise clone from the current fiber
if (memoCache == null) {
const current: Fiber | null = currentlyRenderingFiber.alternate;
if (current !== null) {
const currentUpdateQueue: FunctionComponentUpdateQueue | null =
(current.updateQueue: any);
if (currentUpdateQueue !== null) {
const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;
if (currentMemoCache != null) {
memoCache = {
data: currentMemoCache.data.map(array => array.slice()),
index: 0,
};
}
}
}
}
// Finally fall back to allocating a fresh instance of the cache
if (memoCache == null) {
memoCache = {
data: [],
index: 0,
};
}
if (updateQueue === null) {
updateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = updateQueue;
}
updateQueue.memoCache = memoCache;
let data = memoCache.data[memoCache.index];
if (data === undefined) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) {
data[i] = REACT_MEMO_CACHE_SENTINEL;
}
} else if (data.length !== size) {
// TODO: consider warning or throwing here
if (__DEV__) {
console.error(
'Expected a constant size argument for each invocation of useMemoCache. ' +
'The previous cache was allocated with size %s but size %s was requested.',
data.length,
size,
);
}
}
memoCache.index++;
return data;
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, A> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
return updateReducerImpl(hook, ((currentHook: any): Hook), reducer);
}
function updateReducerImpl<S, A>(
hook: Hook,
current: Hook,
reducer: (S, A) => S,
): [S, Dispatch<A>] {
const queue = hook.queue;
if (queue === null) {
throw new Error(
'Should have a queue. This is likely a bug in React. Please file an issue.',
);
}
queue.lastRenderedReducer = reducer;
// The last rebase update that is NOT part of the base state.
let baseQueue = hook.baseQueue;
// The last pending update that hasn't been processed yet.
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// We have new updates that haven't been processed yet.
// We'll add them to the base queue.
if (baseQueue !== null) {
// Merge the pending queue and the base queue.
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
if (__DEV__) {
if (current.baseQueue !== baseQueue) {
// Internal invariant that should never happen, but feasibly could in
// the future if we implement resuming, or some form of that.
console.error(
'Internal error: Expected work-in-progress queue to be a clone. ' +
'This is a bug in React.',
);
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
// We have a queue to process.
const first = baseQueue.next;
let newState = hook.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast: Update<S, A> | null = null;
let update = first;
do {
// An extra OffscreenLane bit is added to updates that were made to
// a hidden tree, so that we can distinguish them from updates that were
// already there when the tree was hidden.
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;
// Check if this update was made while the tree was hidden. If so, then
// it's not a "base" update and we should disregard the extra base lanes
// that were added to renderLanes when we entered the Offscreen tree.
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
lane: updateLane,
revertLane: update.revertLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
// This update does have sufficient priority.
// Check if this is an optimistic update.
const revertLane = update.revertLane;
if (!enableAsyncActions || revertLane === NoLane) {
// This is not an optimistic update, and we're going to apply it now.
// But, if there were earlier updates that were skipped, we need to
// leave this update in the queue so it can be rebased later.
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
revertLane: NoLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
} else {
// This is an optimistic update. If the "revert" priority is
// sufficient, don't apply the update. Otherwise, apply the update,
// but leave it in the queue so it can be either reverted or
// rebased in a subsequent render.
if (isSubsetOfLanes(renderLanes, revertLane)) {
// The transition that this optimistic update is associated with
// has finished. Pretend the update doesn't exist by skipping
// over it.
update = update.next;
continue;
} else {
const clone: Update<S, A> = {
// Once we commit an optimistic update, we shouldn't uncommit it
// until the transition it is associated with has finished
// (represented by revertLane). Using NoLane here works because 0
// is a subset of all bitmasks, so this will never be skipped by
// the check above.
lane: NoLane,
// Reuse the same revertLane so we know when the transition
// has finished.
revertLane: update.revertLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
revertLane,
);
markSkippedUpdateLanes(revertLane);
}
}
// Process this update.
const action = update.action;
if (shouldDoubleInvokeUserFnsInHooksDEV) {
reducer(newState, action);
}
if (update.hasEagerState) {
// If this update is a state update (not a reducer) and was processed eagerly,
// we can use the eagerly computed state
newState = ((update.eagerState: any): S);
} else {
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
function rerenderReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
if (queue === null) {
throw new Error(
'Should have a queue. This is likely a bug in React. Please file an issue.',
);
}
queue.lastRenderedReducer = reducer;
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
const dispatch: Dispatch<A> = (queue.dispatch: any);
const lastRenderPhaseUpdate = queue.pending;
let newState = hook.memoizedState;
if (lastRenderPhaseUpdate !== null) {
// The queue doesn't persist past this render pass.
queue.pending = null;
const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== firstRenderPhaseUpdate);
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
// Don't persist the state accumulated from the render phase updates to
// the base state unless the queue is empty.
// TODO: Not sure if this is the desired semantics, but it's what we
// do for gDSFP. I can't remember why.
if (hook.baseQueue === null) {
hook.baseState = newState;
}
queue.lastRenderedState = newState;
}
return [newState, dispatch];
}
function mountSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
const fiber = currentlyRenderingFiber;
const hook = mountWorkInProgressHook();
let nextSnapshot;
const isHydrating = getIsHydrating();
if (isHydrating) {
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
nextSnapshot = getServerSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
if (nextSnapshot !== getServerSnapshot()) {
console.error(
'The result of getServerSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
} else {
nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
// Unless we're rendering a blocking lane, schedule a consistency check.
// Right before committing, we will walk the tree and check if any of the
// stores were mutated.
//
// We won't do this if we're hydrating server-rendered content, because if
// the content is stale, it's already visible anyway. Instead we'll patch
// it up in a passive effect.
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
const rootRenderLanes = getWorkInProgressRootRenderLanes();
if (!includesBlockingLane(root, rootRenderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
// Read the current snapshot from the store on every render. This breaks the
// normal rules of React, and only works because store updates are
// always synchronous.
hook.memoizedState = nextSnapshot;
const inst: StoreInstance<T> = {
value: nextSnapshot,
getSnapshot,
};
hook.queue = inst;
// Schedule an effect to subscribe to the store.
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
// Schedule an effect to update the mutable instance fields. We will update
// this whenever subscribe, getSnapshot, or value changes. Because there's no
// clean-up function, and we track the deps correctly, we can call pushEffect
// directly, without storing any additional state. For the same reason, we
// don't need to set a static flag, either.
fiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
createEffectInstance(),
null,
);
return nextSnapshot;
}
function updateSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
const fiber = currentlyRenderingFiber;
const hook = updateWorkInProgressHook();
// Read the current snapshot from the store on every render. This breaks the
// normal rules of React, and only works because store updates are
// always synchronous.
let nextSnapshot;
const isHydrating = getIsHydrating();
if (isHydrating) {
// Needed for strict mode double render
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
nextSnapshot = getServerSnapshot();
} else {
nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
}
const prevSnapshot = (currentHook || hook).memoizedState;
const snapshotChanged = !is(prevSnapshot, nextSnapshot);
if (snapshotChanged) {
hook.memoizedState = nextSnapshot;
markWorkInProgressReceivedUpdate();
}
const inst = hook.queue;
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [
subscribe,
]);
// Whenever getSnapshot or subscribe changes, we need to check in the
// commit phase if there was an interleaved mutation. In concurrent mode
// this can happen all the time, but even in synchronous mode, an earlier
// effect may have mutated the store.
if (
inst.getSnapshot !== getSnapshot ||
snapshotChanged ||
// Check if the subscribe function changed. We can save some memory by
// checking whether we scheduled a subscription effect above.
(workInProgressHook !== null &&
workInProgressHook.memoizedState.tag & HookHasEffect)
) {
fiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
createEffectInstance(),
null,
);
// Unless we're rendering a blocking lane, schedule a consistency check.
// Right before committing, we will walk the tree and check if any of the
// stores were mutated.
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
return nextSnapshot;
}
function pushStoreConsistencyCheck<T>(
fiber: Fiber,
getSnapshot: () => T,
renderedSnapshot: T,
): void {
fiber.flags |= StoreConsistency;
const check: StoreConsistencyCheck<T> = {
getSnapshot,
value: renderedSnapshot,
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.stores = [check];
} else {
const stores = componentUpdateQueue.stores;
if (stores === null) {
componentUpdateQueue.stores = [check];
} else {
stores.push(check);
}
}
}
function updateStoreInstance<T>(
fiber: Fiber,
inst: StoreInstance<T>,
nextSnapshot: T,
getSnapshot: () => T,
): void {
// These are updated in the passive phase
inst.value = nextSnapshot;
inst.getSnapshot = getSnapshot;
// Something may have been mutated in between render and commit. This could
// have been in an event that fired before the passive effects, or it could
// have been in a layout effect. In that case, we would have used the old
// snapsho and getSnapshot values to bail out. We need to check one more time.
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceStoreRerender(fiber);
}
}
function subscribeToStore<T>(
fiber: Fiber,
inst: StoreInstance<T>,
subscribe: (() => void) => () => void,
): any {
const handleStoreChange = () => {
// The store changed. Check if the snapshot changed since the last time we
// read from the store.
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceStoreRerender(fiber);
}
};
// Subscribe to the store and return a clean-up function.
return subscribe(handleStoreChange);
}
function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {
const latestGetSnapshot = inst.getSnapshot;
const prevValue = inst.value;
try {
const nextValue = latestGetSnapshot();
return !is(prevValue, nextValue);
} catch (error) {
return true;
}
}
function forceStoreRerender(fiber: Fiber) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane);
}
}
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
return hook;
}
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any);
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, initialState);
}
function rerenderState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return rerenderReducer(basicStateReducer, initialState);
}
function mountOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
const hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = passthrough;
const queue: UpdateQueue<S, A> = {
pending: null,
lanes: NoLanes,
dispatch: null,
// Optimistic state does not use the eager update optimization.
lastRenderedReducer: null,
lastRenderedState: null,
};
hook.queue = queue;
// This is different than the normal setState function.
const dispatch: A => void = (dispatchOptimisticSetState.bind(
null,
currentlyRenderingFiber,
true,
queue,
): any);
queue.dispatch = dispatch;
return [passthrough, dispatch];
}
function updateOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
const hook = updateWorkInProgressHook();
return updateOptimisticImpl(
hook,
((currentHook: any): Hook),
passthrough,
reducer,
);
}
function updateOptimisticImpl<S, A>(
hook: Hook,
current: Hook | null,
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
// Optimistic updates are always rebased on top of the latest value passed in
// as an argument. It's called a passthrough because if there are no pending
// updates, it will be returned as-is.
//
// Reset the base state to the passthrough. Future updates will be applied
// on top of this.
hook.baseState = passthrough;
// If a reducer is not provided, default to the same one used by useState.
const resolvedReducer: (S, A) => S =
typeof reducer === 'function' ? reducer : (basicStateReducer: any);
return updateReducerImpl(hook, ((currentHook: any): Hook), resolvedReducer);
}
function rerenderOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
// Unlike useState, useOptimistic doesn't support render phase updates.
// Also unlike useState, we need to replay all pending updates again in case
// the passthrough value changed.
//
// So instead of a forked re-render implementation that knows how to handle
// render phase udpates, we can use the same implementation as during a
// regular mount or update.
const hook = updateWorkInProgressHook();
if (currentHook !== null) {
// This is an update. Process the update queue.
return updateOptimisticImpl(
hook,
((currentHook: any): Hook),
passthrough,
reducer,
);
}
// This is a mount. No updates to process.
// Reset the base state to the passthrough. Future updates will be applied
// on top of this.
hook.baseState = passthrough;
const dispatch = hook.queue.dispatch;
return [passthrough, dispatch];
}
// useFormState actions run sequentially, because each action receives the
// previous state as an argument. We store pending actions on a queue.
type FormStateActionQueue<S, P> = {
// This is the most recent state returned from an action. It's updated as
// soon as the action finishes running.
state: Awaited<S>,
// A stable dispatch method, passed to the user.
dispatch: Dispatch<P>,
// This is the most recent action function that was rendered. It's updated
// during the commit phase.
action: (Awaited<S>, P) => S,
// This is a circular linked list of pending action payloads. It incudes the
// action that is currently running.
pending: FormStateActionQueueNode<P> | null,
};
type FormStateActionQueueNode<P> = {
payload: P,
// This is never null because it's part of a circular linked list.
next: FormStateActionQueueNode<P>,
};
function dispatchFormState<S, P>(
fiber: Fiber,
actionQueue: FormStateActionQueue<S, P>,
setState: Dispatch<S | Awaited<S>>,
payload: P,
): void {
if (isRenderPhaseUpdate(fiber)) {
throw new Error('Cannot update form state while rendering.');
}
const last = actionQueue.pending;
if (last === null) {
// There are no pending actions; this is the first one. We can run
// it immediately.
const newLast: FormStateActionQueueNode<P> = {
payload,
next: (null: any), // circular
};
newLast.next = actionQueue.pending = newLast;
runFormStateAction(actionQueue, (setState: any), payload);
} else {
// There's already an action running. Add to the queue.
const first = last.next;
const newLast: FormStateActionQueueNode<P> = {
payload,
next: first,
};
actionQueue.pending = last.next = newLast;
}
}
function runFormStateAction<S, P>(
actionQueue: FormStateActionQueue<S, P>,
setState: Dispatch<S | Awaited<S>>,
payload: P,
) {
const action = actionQueue.action;
const prevState = actionQueue.state;
// This is a fork of startTransition
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = ({}: BatchConfigTransition);
const currentTransition = ReactCurrentBatchConfig.transition;
if (__DEV__) {
ReactCurrentBatchConfig.transition._updatedFibers = new Set();
}
try {
const returnValue = action(prevState, payload);
if (
returnValue !== null &&
typeof returnValue === 'object' &&
// $FlowFixMe[method-unbinding]
typeof returnValue.then === 'function'
) {
const thenable = ((returnValue: any): Thenable<Awaited<S>>);
// Attach a listener to read the return state of the action. As soon as
// this resolves, we can run the next action in the sequence.
thenable.then(
(nextState: Awaited<S>) => {
actionQueue.state = nextState;
finishRunningFormStateAction(actionQueue, (setState: any));
},
() => finishRunningFormStateAction(actionQueue, (setState: any)),
);
const entangledResult = requestAsyncActionContext<S>(thenable, null);
setState((entangledResult: any));
} else {
// This is either `returnValue` or a thenable that resolves to
// `returnValue`, depending on whether we're inside an async action scope.
const entangledResult = requestSyncActionContext<S>(returnValue, null);
setState((entangledResult: any));
const nextState = ((returnValue: any): Awaited<S>);
actionQueue.state = nextState;
finishRunningFormStateAction(actionQueue, (setState: any));
}
} catch (error) {
// This is a trick to get the `useFormState` hook to rethrow the error.
// When it unwraps the thenable with the `use` algorithm, the error
// will be thrown.
const rejectedThenable: S = ({
then() {},
status: 'rejected',
reason: error,
// $FlowFixMe: Not sure why this doesn't work
}: RejectedThenable<Awaited<S>>);
setState(rejectedThenable);
finishRunningFormStateAction(actionQueue, (setState: any));
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
if (__DEV__) {
if (prevTransition === null && currentTransition._updatedFibers) {
const updatedFibersCount = currentTransition._updatedFibers.size;
currentTransition._updatedFibers.clear();
if (updatedFibersCount > 10) {
console.warn(
'Detected a large number of updates inside startTransition. ' +
'If this is due to a subscription please re-write it to use React provided hooks. ' +
'Otherwise concurrent mode guarantees are off the table.',
);
}
}
}
}
}
function finishRunningFormStateAction<S, P>(
actionQueue: FormStateActionQueue<S, P>,
setState: Dispatch<S | Awaited<S>>,
) {
// The action finished running. Pop it from the queue and run the next pending
// action, if there are any.
const last = actionQueue.pending;
if (last !== null) {
const first = last.next;
if (first === last) {
// This was the last action in the queue.
actionQueue.pending = null;
} else {
// Remove the first node from the circular queue.
const next = first.next;
last.next = next;
// Run the next action.
runFormStateAction(actionQueue, (setState: any), next.payload);
}
}
}
function formStateReducer<S>(oldState: S, newState: S): S {
return newState;
}
function mountFormState<S, P>(
action: (Awaited<S>, P) => S,
initialStateProp: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
let initialState: Awaited<S> = initialStateProp;
if (getIsHydrating()) {
const root: FiberRoot = (getWorkInProgressRoot(): any);
const ssrFormState = root.formState;
// If a formState option was passed to the root, there are form state
// markers that we need to hydrate. These indicate whether the form state
// matches this hook instance.
if (ssrFormState !== null) {
const isMatching = tryToClaimNextHydratableFormMarkerInstance(
currentlyRenderingFiber,
);
if (isMatching) {
initialState = ssrFormState[0];
}
}
}
// State hook. The state is stored in a thenable which is then unwrapped by
// the `use` algorithm during render.
const stateHook = mountWorkInProgressHook();
stateHook.memoizedState = stateHook.baseState = initialState;
// TODO: Typing this "correctly" results in recursion limit errors
// const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {
const stateQueue = {
pending: null,
lanes: NoLanes,
dispatch: (null: any),
lastRenderedReducer: formStateReducer,
lastRenderedState: initialState,
};
stateHook.queue = stateQueue;
const setState: Dispatch<S | Awaited<S>> = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
((stateQueue: any): UpdateQueue<S | Awaited<S>, S | Awaited<S>>),
): any);
stateQueue.dispatch = setState;
// Action queue hook. This is used to queue pending actions. The queue is
// shared between all instances of the hook. Similar to a regular state queue,
// but different because the actions are run sequentially, and they run in
// an event instead of during render.
const actionQueueHook = mountWorkInProgressHook();
const actionQueue: FormStateActionQueue<S, P> = {
state: initialState,
dispatch: (null: any), // circular
action,
pending: null,
};
actionQueueHook.queue = actionQueue;
const dispatch = (dispatchFormState: any).bind(
null,
currentlyRenderingFiber,
actionQueue,
setState,
);
actionQueue.dispatch = dispatch;
// Stash the action function on the memoized state of the hook. We'll use this
// to detect when the action function changes so we can update it in
// an effect.
actionQueueHook.memoizedState = action;
return [initialState, dispatch];
}
function updateFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
const stateHook = updateWorkInProgressHook();
const currentStateHook = ((currentHook: any): Hook);
return updateFormStateImpl(
stateHook,
currentStateHook,
action,
initialState,
permalink,
);
}
function updateFormStateImpl<S, P>(
stateHook: Hook,
currentStateHook: Hook,
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
const [actionResult] = updateReducerImpl<S | Thenable<S>, S | Thenable<S>>(
stateHook,
currentStateHook,
formStateReducer,
);
// This will suspend until the action finishes.
const state: Awaited<S> =
typeof actionResult === 'object' &&
actionResult !== null &&
// $FlowFixMe[method-unbinding]
typeof actionResult.then === 'function'
? useThenable(((actionResult: any): Thenable<Awaited<S>>))
: (actionResult: any);
const actionQueueHook = updateWorkInProgressHook();
const actionQueue = actionQueueHook.queue;
const dispatch = actionQueue.dispatch;
// Check if a new action was passed. If so, update it in an effect.
const prevAction = actionQueueHook.memoizedState;
if (action !== prevAction) {
currentlyRenderingFiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
formStateActionEffect.bind(null, actionQueue, action),
createEffectInstance(),
null,
);
}
return [state, dispatch];
}
function formStateActionEffect<S, P>(
actionQueue: FormStateActionQueue<S, P>,
action: (Awaited<S>, P) => S,
): void {
actionQueue.action = action;
}
function rerenderFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
// Unlike useState, useFormState doesn't support render phase updates.
// Also unlike useState, we need to replay all pending updates again in case
// the passthrough value changed.
//
// So instead of a forked re-render implementation that knows how to handle
// render phase udpates, we can use the same implementation as during a
// regular mount or update.
const stateHook = updateWorkInProgressHook();
const currentStateHook = currentHook;
if (currentStateHook !== null) {
// This is an update. Process the update queue.
return updateFormStateImpl(
stateHook,
currentStateHook,
action,
initialState,
permalink,
);
}
// This is a mount. No updates to process.
const state: Awaited<S> = stateHook.memoizedState;
const actionQueueHook = updateWorkInProgressHook();
const actionQueue = actionQueueHook.queue;
const dispatch = actionQueue.dispatch;
// This may have changed during the rerender.
actionQueueHook.memoizedState = action;
return [state, dispatch];
}
function pushEffect(
tag: HookFlags,
create: () => (() => void) | void,
inst: EffectInstance,
deps: Array<mixed> | null,
): Effect {
const effect: Effect = {
tag,
create,
inst,
deps,
// Circular
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createEffectInstance(): EffectInstance {
return {destroy: undefined};
}
let stackContainsErrorMessage: boolean | null = null;
function getCallerStackFrame(): string {
// eslint-disable-next-line react-internal/prod-error-codes
const stackFrames = new Error('Error message').stack.split('\n');
// Some browsers (e.g. Chrome) include the error message in the stack
// but others (e.g. Firefox) do not.
if (stackContainsErrorMessage === null) {
stackContainsErrorMessage = stackFrames[0].includes('Error message');
}
return stackContainsErrorMessage
? stackFrames.slice(3, 4).join('\n')
: stackFrames.slice(2, 3).join('\n');
}
function mountRef<T>(initialValue: T): {current: T} {
const hook = mountWorkInProgressHook();
if (enableUseRefAccessWarning) {
if (__DEV__) {
// Support lazy initialization pattern shown in docs.
// We need to store the caller stack frame so that we don't warn on subsequent renders.
let hasBeenInitialized = initialValue != null;
let lazyInitGetterStack = null;
let didCheckForLazyInit = false;
// Only warn once per component+hook.
let didWarnAboutRead = false;
let didWarnAboutWrite = false;
let current = initialValue;
const ref = {
get current() {
if (!hasBeenInitialized) {
didCheckForLazyInit = true;
lazyInitGetterStack = getCallerStackFrame();
} else if (currentlyRenderingFiber !== null && !didWarnAboutRead) {
if (
lazyInitGetterStack === null ||
lazyInitGetterStack !== getCallerStackFrame()
) {
didWarnAboutRead = true;
console.warn(
'%s: Unsafe read of a mutable value during render.\n\n' +
'Reading from a ref during render is only safe if:\n' +
'1. The ref value has not been updated, or\n' +
'2. The ref holds a lazily-initialized value that is only set once.\n',
getComponentNameFromFiber(currentlyRenderingFiber) || 'Unknown',
);
}
}
return current;
},
set current(value: any) {
if (currentlyRenderingFiber !== null && !didWarnAboutWrite) {
if (hasBeenInitialized || !didCheckForLazyInit) {
didWarnAboutWrite = true;
console.warn(
'%s: Unsafe write of a mutable value during render.\n\n' +
'Writing to a ref during render is only safe if the ref holds ' +
'a lazily-initialized value that is only set once.\n',
getComponentNameFromFiber(currentlyRenderingFiber) || 'Unknown',
);
}
}
hasBeenInitialized = true;
current = value;
},
};
Object.seal(ref);
hook.memoizedState = ref;
return ref;
} else {
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
} else {
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
}
function updateRef<T>(initialValue: T): {current: T} {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
function mountEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
createEffectInstance(),
nextDeps,
);
}
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// currentHook is null on initial mount when rerendering after a render phase
// state update or for strict mode.
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
inst,
nextDeps,
);
}
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
) {
mountEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
} else {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
}
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
payload: EventFunctionPayload<Args, Return, F>,
) {
currentlyRenderingFiber.flags |= UpdateEffect;
let componentUpdateQueue: null | FunctionComponentUpdateQueue =
(currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.events = [payload];
} else {
const events = componentUpdateQueue.events;
if (events === null) {
componentUpdateQueue.events = [payload];
} else {
events.push(payload);
}
}
}
function mountEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
const hook = mountWorkInProgressHook();
const ref = {impl: callback};
hook.memoizedState = ref;
// $FlowIgnore[incompatible-return]
return function eventFn() {
if (isInvalidExecutionContextForEventFunction()) {
throw new Error(
"A function wrapped in useEffectEvent can't be called during rendering.",
);
}
return ref.impl.apply(undefined, arguments);
};
}
function updateEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
const hook = updateWorkInProgressHook();
const ref = hook.memoizedState;
useEffectEventImpl({ref, nextImpl: callback});
// $FlowIgnore[incompatible-return]
return function eventFn() {
if (isInvalidExecutionContextForEventFunction()) {
throw new Error(
"A function wrapped in useEffectEvent can't be called during rendering.",
);
}
return ref.impl.apply(undefined, arguments);
};
}
function mountInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
}
function updateInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
}
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
) {
fiberFlags |= MountLayoutDevEffect;
}
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function imperativeHandleEffect<T>(
create: () => T,
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
): void | (() => void) {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
if (__DEV__) {
if (!refObject.hasOwnProperty('current')) {
console.error(
'Expected useImperativeHandle() first argument to either be a ' +
'ref callback or React.createRef() object. Instead received: %s.',
'an object with keys {' + Object.keys(refObject).join(', ') + '}',
);
}
}
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
function mountImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
if (typeof create !== 'function') {
console.error(
'Expected useImperativeHandle() second argument to be a function ' +
'that creates a handle. Instead received: %s.',
create !== null ? typeof create : 'null',
);
}
}
// TODO: If deps are provided, should we skip comparing the ref itself?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
) {
fiberFlags |= MountLayoutDevEffect;
}
mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
function updateImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
if (typeof create !== 'function') {
console.error(
'Expected useImperativeHandle() second argument to be a function ' +
'that creates a handle. Instead received: %s.',
create !== null ? typeof create : 'null',
);
}
}
// TODO: If deps are provided, should we skip comparing the ref itself?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
// This hook is normally a no-op.
// The react-debug-hooks package injects its own implementation
// so that e.g. DevTools can display custom hook values.
}
const updateDebugValue = mountDebugValue;
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
if (shouldDoubleInvokeUserFnsInHooksDEV) {
nextCreate();
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
if (shouldDoubleInvokeUserFnsInHooksDEV) {
nextCreate();
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function mountDeferredValue<T>(value: T, initialValue?: T): T {
const hook = mountWorkInProgressHook();
return mountDeferredValueImpl(hook, value, initialValue);
}
function updateDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
const resolvedCurrentHook: Hook = (currentHook: any);
const prevValue: T = resolvedCurrentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value, initialValue);
}
function rerenderDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
if (currentHook === null) {
// This is a rerender during a mount.
return mountDeferredValueImpl(hook, value, initialValue);
} else {
// This is a rerender during an update.
const prevValue: T = currentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value, initialValue);
}
}
function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
if (
enableUseDeferredValueInitialArg &&
// When `initialValue` is provided, we defer the initial render even if the
// current render is not synchronous.
initialValue !== undefined &&
// However, to avoid waterfalls, we do not defer if this render
// was itself spawned by an earlier useDeferredValue. Check if DeferredLane
// is part of the render lanes.
!includesSomeLane(renderLanes, DeferredLane)
) {
// Render with the initial value
hook.memoizedState = initialValue;
// Schedule a deferred render to switch to the final value.
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
return initialValue;
} else {
hook.memoizedState = value;
return value;
}
}
function updateDeferredValueImpl<T>(
hook: Hook,
prevValue: T,
value: T,
initialValue?: T,
): T {
if (is(value, prevValue)) {
// The incoming value is referentially identical to the currently rendered
// value, so we can bail out quickly.
return value;
} else {
// Received a new value that's different from the current value.
// Check if we're inside a hidden tree
if (isCurrentTreeHidden()) {
// Revealing a prerendered tree is considered the same as mounting new
// one, so we reuse the "mount" path in this case.
const resultValue = mountDeferredValueImpl(hook, value, initialValue);
// Unlike during an actual mount, we need to mark this as an update if
// the value changed.
if (!is(resultValue, prevValue)) {
markWorkInProgressReceivedUpdate();
}
return resultValue;
}
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
if (shouldDeferValue) {
// This is an urgent update. Since the value has changed, keep using the
// previous value and spawn a deferred render to update it later.
// Schedule a deferred render
const deferredLane = requestDeferredLane();
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
deferredLane,
);
markSkippedUpdateLanes(deferredLane);
// Reuse the previous value. We do not need to mark this as an update,
// because we did not render a new value.
return prevValue;
} else {
// This is not an urgent update, so we can use the latest value regardless
// of what it is. No need to defer it.
// Mark this as an update to prevent the fiber from bailing out.
markWorkInProgressReceivedUpdate();
hook.memoizedState = value;
return value;
}
}
}
function startTransition<S>(
fiber: Fiber,
queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,
pendingState: S,
finishedState: S,
callback: () => mixed,
options?: StartTransitionOptions,
): void {
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
const prevTransition = ReactCurrentBatchConfig.transition;
const currentTransition: BatchConfigTransition = {};
if (enableAsyncActions) {
// We don't really need to use an optimistic update here, because we
// schedule a second "revert" update below (which we use to suspend the
// transition until the async action scope has finished). But we'll use an
// optimistic update anyway to make it less likely the behavior accidentally
// diverges; for example, both an optimistic update and this one should
// share the same lane.
ReactCurrentBatchConfig.transition = currentTransition;
dispatchOptimisticSetState(fiber, false, queue, pendingState);
} else {
ReactCurrentBatchConfig.transition = null;
dispatchSetState(fiber, queue, pendingState);
ReactCurrentBatchConfig.transition = currentTransition;
}
if (enableTransitionTracing) {
if (options !== undefined && options.name !== undefined) {
ReactCurrentBatchConfig.transition.name = options.name;
ReactCurrentBatchConfig.transition.startTime = now();
}
}
if (__DEV__) {
ReactCurrentBatchConfig.transition._updatedFibers = new Set();
}
try {
if (enableAsyncActions) {
const returnValue = callback();
// Check if we're inside an async action scope. If so, we'll entangle
// this new action with the existing scope.
//
// If we're not already inside an async action scope, and this action is
// async, then we'll create a new async scope.
//
// In the async case, the resulting render will suspend until the async
// action scope has finished.
if (
returnValue !== null &&
typeof returnValue === 'object' &&
typeof returnValue.then === 'function'
) {
const thenable = ((returnValue: any): Thenable<mixed>);
// This is a thenable that resolves to `finishedState` once the async
// action scope has finished.
const entangledResult = requestAsyncActionContext(
thenable,
finishedState,
);
dispatchSetState(fiber, queue, entangledResult);
} else {
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
const entangledResult = requestSyncActionContext(
returnValue,
finishedState,
);
dispatchSetState(fiber, queue, entangledResult);
}
} else {
// Async actions are not enabled.
dispatchSetState(fiber, queue, finishedState);
callback();
}
} catch (error) {
if (enableAsyncActions) {
// This is a trick to get the `useTransition` hook to rethrow the error.
// When it unwraps the thenable with the `use` algorithm, the error
// will be thrown.
const rejectedThenable: RejectedThenable<S> = {
then() {},
status: 'rejected',
reason: error,
};
dispatchSetState(fiber, queue, rejectedThenable);
} else {
// The error rethrowing behavior is only enabled when the async actions
// feature is on, even for sync actions.
throw error;
}
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
if (__DEV__) {
if (prevTransition === null && currentTransition._updatedFibers) {
const updatedFibersCount = currentTransition._updatedFibers.size;
currentTransition._updatedFibers.clear();
if (updatedFibersCount > 10) {
console.warn(
'Detected a large number of updates inside startTransition. ' +
'If this is due to a subscription please re-write it to use React provided hooks. ' +
'Otherwise concurrent mode guarantees are off the table.',
);
}
}
}
}
}
export function startHostTransition<F>(
formFiber: Fiber,
pendingState: TransitionStatus,
callback: F => mixed,
formData: F,
): void {
if (!enableFormActions) {
// Not implemented.
return;
}
if (!enableAsyncActions) {
// Form actions are enabled, but async actions are not. Call the function,
// but don't handle any pending or error states.
callback(formData);
return;
}
if (formFiber.tag !== HostComponent) {
throw new Error(
'Expected the form instance to be a HostComponent. This ' +
'is a bug in React.',
);
}
let queue: UpdateQueue<
Thenable<TransitionStatus> | TransitionStatus,
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
>;
if (formFiber.memoizedState === null) {
// Upgrade this host component fiber to be stateful. We're going to pretend
// it was stateful all along so we can reuse most of the implementation
// for function components and useTransition.
//
// Create the state hook used by TransitionAwareHostComponent. This is
// essentially an inlined version of mountState.
const newQueue: UpdateQueue<
Thenable<TransitionStatus> | TransitionStatus,
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
> = {
pending: null,
lanes: NoLanes,
// We're going to cheat and intentionally not create a bound dispatch
// method, because we can call it directly in startTransition.
dispatch: (null: any),
lastRenderedReducer: basicStateReducer,
lastRenderedState: NoPendingHostTransition,
};
queue = newQueue;
const stateHook: Hook = {
memoizedState: NoPendingHostTransition,
baseState: NoPendingHostTransition,
baseQueue: null,
queue: newQueue,
next: null,
};
// Add the state hook to both fiber alternates. The idea is that the fiber
// had this hook all along.
formFiber.memoizedState = stateHook;
const alternate = formFiber.alternate;
if (alternate !== null) {
alternate.memoizedState = stateHook;
}
} else {
// This fiber was already upgraded to be stateful.
const stateHook: Hook = formFiber.memoizedState;
queue = stateHook.queue;
}
startTransition(
formFiber,
queue,
pendingState,
NoPendingHostTransition,
// TODO: We can avoid this extra wrapper, somehow. Figure out layering
// once more of this function is implemented.
() => callback(formData),
);
}
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const stateHook = mountStateImpl((false: Thenable<boolean> | boolean));
// The `start` method never changes.
const start = startTransition.bind(
null,
currentlyRenderingFiber,
stateHook.queue,
true,
false,
);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [false, start];
}
function updateTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [booleanOrThenable] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
const isPending =
typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: // This will suspend until the async action scope has finished.
useThenable(booleanOrThenable);
return [isPending, start];
}
function rerenderTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [booleanOrThenable] = rerenderState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
const isPending =
typeof booleanOrThenable === 'boolean'
? booleanOrThenable
: // This will suspend until the async action scope has finished.
useThenable(booleanOrThenable);
return [isPending, start];
}
function useHostTransitionStatus(): TransitionStatus {
if (!(enableFormActions && enableAsyncActions)) {
throw new Error('Not implemented.');
}
const status: TransitionStatus | null = readContext(HostTransitionContext);
return status !== null ? status : NoPendingHostTransition;
}
function mountId(): string {
const hook = mountWorkInProgressHook();
const root = ((getWorkInProgressRoot(): any): FiberRoot);
// TODO: In Fizz, id generation is specific to each server config. Maybe we
// should do this in Fiber, too? Deferring this decision for now because
// there's no other place to store the prefix except for an internal field on
// the public createRoot object, which the fiber tree does not currently have
// a reference to.
const identifierPrefix = root.identifierPrefix;
let id;
if (getIsHydrating()) {
const treeId = getTreeId();
// Use a captial R prefix for server-generated ids.
id = ':' + identifierPrefix + 'R' + treeId;
// Unless this is the first id at this level, append a number at the end
// that represents the position of this useId hook among all the useId
// hooks for this fiber.
const localId = localIdCounter++;
if (localId > 0) {
id += 'H' + localId.toString(32);
}
id += ':';
} else {
// Use a lowercase r prefix for client-generated ids.
const globalClientId = globalClientIdCounter++;
id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
}
hook.memoizedState = id;
return id;
}
function updateId(): string {
const hook = updateWorkInProgressHook();
const id: string = hook.memoizedState;
return id;
}
function mountRefresh(): any {
const hook = mountWorkInProgressHook();
const refresh = (hook.memoizedState = refreshCache.bind(
null,
currentlyRenderingFiber,
));
return refresh;
}
function updateRefresh(): any {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
if (!enableCache) {
return;
}
// TODO: Does Cache work in legacy mode? Should decide and write a test.
// TODO: Consider warning if the refresh is at discrete priority, or if we
// otherwise suspect that it wasn't batched properly.
let provider = fiber.return;
while (provider !== null) {
switch (provider.tag) {
case CacheComponent:
case HostRoot: {
// Schedule an update on the cache boundary to trigger a refresh.
const lane = requestUpdateLane(provider);
const refreshUpdate = createLegacyQueueUpdate(lane);
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, provider, lane);
entangleLegacyQueueTransitions(root, provider, lane);
}
// TODO: If a refresh never commits, the new cache created here must be
// released. A simple case is start refreshing a cache boundary, but then
// unmount that boundary before the refresh completes.
const seededCache = createCache();
if (seedKey !== null && seedKey !== undefined && root !== null) {
if (enableLegacyCache) {
// Seed the cache with the value passed by the caller. This could be
// from a server mutation, or it could be a streaming response.
seededCache.data.set(seedKey, seedValue);
} else {
if (__DEV__) {
console.error(
'The seed argument is not enabled outside experimental channels.',
);
}
}
}
const payload = {
cache: seededCache,
};
refreshUpdate.payload = payload;
return;
}
}
provider = provider.return;
}
// TODO: Warn if unmounted?
}
function dispatchReducerAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
if (__DEV__) {
if (typeof arguments[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
revertLane: NoLane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane, action);
}
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
): void {
if (__DEV__) {
if (typeof arguments[3] === 'function') {
console.error(
"State updates from the useState() and useReducer() Hooks don't support the " +
'second callback argument. To execute a side effect after ' +
'rendering, declare it in the component body with useEffect().',
);
}
}
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
revertLane: NoLane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
if (__DEV__) {
prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
// TODO: Do we still need to entangle transitions in this case?
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, lane);
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane, action);
}
function dispatchOptimisticSetState<S, A>(
fiber: Fiber,
throwIfDuringRender: boolean,
queue: UpdateQueue<S, A>,
action: A,
): void {
if (__DEV__) {
if (ReactCurrentBatchConfig.transition === null) {
// An optimistic update occurred, but startTransition is not on the stack.
// There are two likely scenarios.
// One possibility is that the optimistic update is triggered by a regular
// event handler (e.g. `onSubmit`) instead of an action. This is a mistake
// and we will warn.
// The other possibility is the optimistic update is inside an async
// action, but after an `await`. In this case, we can make it "just work"
// by associating the optimistic update with the pending async action.
// Technically it's possible that the optimistic update is unrelated to
// the pending action, but we don't have a way of knowing this for sure
// because browsers currently do not provide a way to track async scope.
// (The AsyncContext proposal, if it lands, will solve this in the
// future.) However, this is no different than the problem of unrelated
// transitions being grouped together — it's not wrong per se, but it's
// not ideal.
// Once AsyncContext starts landing in browsers, we will provide better
// warnings in development for these cases.
if (peekEntangledActionLane() !== NoLane) {
// There is a pending async action. Don't warn.
} else {
// There's no pending async action. The most likely cause is that we're
// inside a regular event handler (e.g. onSubmit) instead of an action.
console.error(
'An optimistic state update occurred outside a transition or ' +
'action. To fix, move the update to an action, or wrap ' +
'with startTransition.',
);
}
}
}
const update: Update<S, A> = {
// An optimistic update commits synchronously.
lane: SyncLane,
// After committing, the optimistic update is "reverted" using the same
// lane as the transition it's associated with.
revertLane: requestTransitionLane(),
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) {
// When calling startTransition during render, this warns instead of
// throwing because throwing would be a breaking change. setOptimisticState
// is a new API so it's OK to throw.
if (throwIfDuringRender) {
throw new Error('Cannot update optimistic state while rendering.');
} else {
// startTransition was called during render. We don't need to do anything
// besides warn here because the render phase update would be overidden by
// the second update, anyway. We can remove this branch and make it throw
// in a future release.
if (__DEV__) {
console.error('Cannot call startTransition while rendering.');
}
}
} else {
const root = enqueueConcurrentHookUpdate(fiber, queue, update, SyncLane);
if (root !== null) {
// NOTE: The optimistic update implementation assumes that the transition
// will never be attempted before the optimistic update. This currently
// holds because the optimistic update is always synchronous. If we ever
// change that, we'll need to account for this.
scheduleUpdateOnFiber(root, fiber, SyncLane);
// Optimistic updates are always synchronous, so we don't need to call
// entangleTransitionUpdate here.
}
}
markUpdateInDevTools(fiber, SyncLane, action);
}
function isRenderPhaseUpdate(fiber: Fiber): boolean {
const alternate = fiber.alternate;
return (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
);
}
function enqueueRenderPhaseUpdate<S, A>(
queue: UpdateQueue<S, A>,
update: Update<S, A>,
): void {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate =
true;
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
// TODO: Move to ReactFiberConcurrentUpdates?
function entangleTransitionUpdate<S, A>(
root: FiberRoot,
queue: UpdateQueue<S, A>,
lane: Lane,
): void {
if (isTransitionLane(lane)) {
let queueLanes = queue.lanes;
// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
queue.lanes = newQueueLanes;
// Even if queue.lanes already include lane, we don't know for certain if
// the lane finished since the last time we entangled it. So we need to
// entangle it again, just to be sure.
markRootEntangled(root, newQueueLanes);
}
}
function markUpdateInDevTools<A>(fiber: Fiber, lane: Lane, action: A): void {
if (__DEV__) {
if (enableDebugTracing) {
if (fiber.mode & DebugTracingMode) {
const name = getComponentNameFromFiber(fiber) || 'Unknown';
logStateUpdateScheduled(name, lane, action);
}
}
}
if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
}
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
use,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
};
if (enableCache) {
(ContextOnlyDispatcher: Dispatcher).useCacheRefresh = throwInvalidHookError;
}
if (enableUseMemoCacheHook) {
(ContextOnlyDispatcher: Dispatcher).useMemoCache = throwInvalidHookError;
}
if (enableUseEffectEventHook) {
(ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
}
if (enableFormActions && enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus =
throwInvalidHookError;
(ContextOnlyDispatcher: Dispatcher).useFormState = throwInvalidHookError;
}
if (enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useOptimistic = throwInvalidHookError;
}
const HooksDispatcherOnMount: Dispatcher = {
readContext,
use,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
};
if (enableCache) {
(HooksDispatcherOnMount: Dispatcher).useCacheRefresh = mountRefresh;
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnMount: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMount: Dispatcher).useFormState = mountFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic;
}
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
};
if (enableCache) {
(HooksDispatcherOnUpdate: Dispatcher).useCacheRefresh = updateRefresh;
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnUpdate: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnUpdate: Dispatcher).useFormState = updateFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic;
}
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: rerenderReducer,
useRef: updateRef,
useState: rerenderState,
useDebugValue: updateDebugValue,
useDeferredValue: rerenderDeferredValue,
useTransition: rerenderTransition,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
};
if (enableCache) {
(HooksDispatcherOnRerender: Dispatcher).useCacheRefresh = updateRefresh;
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnRerender: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnRerender: Dispatcher).useFormState = rerenderFormState;
}
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic;
}
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
if (__DEV__) {
const warnInvalidContextAccess = () => {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
};
const warnInvalidHookAccess = () => {
console.error(
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
'You can only call Hooks at the top level of your React function. ' +
'For more information, see ' +
'https://reactjs.org/link/rules-of-hooks',
);
};
HooksDispatcherOnMountInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
mountHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
mountHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
mountHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
mountHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
mountHookTypesDev();
return mountId();
},
};
if (enableCache) {
(HooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
mountHookTypesDev();
return mountRefresh();
};
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnMountInDEV: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
mountHookTypesDev();
return mountEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMountInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
mountHookTypesDev();
return mountOptimistic(passthrough, reducer);
};
}
HooksDispatcherOnMountWithHookTypesInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
updateHookTypesDev();
return mountEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
updateHookTypesDev();
return mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
updateHookTypesDev();
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
updateHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return mountId();
},
};
if (enableCache) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return mountRefresh();
};
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useMemoCache =
useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return mountEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return mountFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return mountOptimistic(passthrough, reducer);
};
}
HooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
updateHookTypesDev();
return updateEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
updateHookTypesDev();
return updateImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return updateInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
updateHookTypesDev();
return updateLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
updateHookTypesDev();
return updateRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return updateDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return updateTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return updateId();
},
};
if (enableCache) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
};
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return updateEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnUpdateInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return updateOptimistic(passthrough, reducer);
};
}
HooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
return readContext(context);
},
use,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
updateHookTypesDev();
return updateEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
updateHookTypesDev();
return updateImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
updateHookTypesDev();
return updateInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
updateHookTypesDev();
return updateLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return rerenderReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
updateHookTypesDev();
return updateRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnRerenderInDEV;
try {
return rerenderState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
updateHookTypesDev();
return rerenderDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
updateHookTypesDev();
return rerenderTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
updateHookTypesDev();
return updateId();
},
};
if (enableCache) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
};
}
if (enableUseMemoCacheHook) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useMemoCache = useMemoCache;
}
if (enableUseEffectEventHook) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
updateHookTypesDev();
return updateEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnRerenderInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
updateHookTypesDev();
return rerenderOptimistic(passthrough, reducer);
};
}
InvalidNestedHooksDispatcherOnMountInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
mountHookTypesDev();
return mountCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
mountHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
warnInvalidHookAccess();
mountHookTypesDev();
return mountImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
warnInvalidHookAccess();
mountHookTypesDev();
return mountRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
warnInvalidHookAccess();
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
mountHookTypesDev();
return mountDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
mountHookTypesDev();
return mountDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
mountHookTypesDev();
return mountTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
mountHookTypesDev();
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
mountHookTypesDev();
return mountId();
},
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
mountHookTypesDev();
return mountRefresh();
};
}
if (enableUseMemoCacheHook) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useMemoCache =
function (size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
};
}
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
mountHookTypesDev();
return mountEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
mountHookTypesDev();
return mountOptimistic(passthrough, reducer);
};
}
InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
warnInvalidHookAccess();
updateHookTypesDev();
return updateImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
warnInvalidHookAccess();
updateHookTypesDev();
return updateRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
updateHookTypesDev();
return updateTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
updateHookTypesDev();
return updateId();
},
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
};
}
if (enableUseMemoCacheHook) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useMemoCache =
function (size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
};
}
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
updateHookTypesDev();
return updateOptimistic(passthrough, reducer);
};
}
InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext<T>(context: ReactContext<T>): T {
warnInvalidContextAccess();
return readContext(context);
},
use<T>(usable: Usable<T>): T {
warnInvalidHookAccess();
return use(usable);
},
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useCallback';
warnInvalidHookAccess();
updateHookTypesDev();
return updateCallback(callback, deps);
},
useContext<T>(context: ReactContext<T>): T {
currentHookNameInDev = 'useContext';
warnInvalidHookAccess();
updateHookTypesDev();
return readContext(context);
},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEffect(create, deps);
},
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useImperativeHandle';
warnInvalidHookAccess();
updateHookTypesDev();
return updateImperativeHandle(ref, create, deps);
},
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useInsertionEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateInsertionEffect(create, deps);
},
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
currentHookNameInDev = 'useLayoutEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateLayoutEffect(create, deps);
},
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
currentHookNameInDev = 'useMemo';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateMemo(create, deps);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return rerenderReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useRef<T>(initialValue: T): {current: T} {
currentHookNameInDev = 'useRef';
warnInvalidHookAccess();
updateHookTypesDev();
return updateRef(initialValue);
},
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
warnInvalidHookAccess();
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current =
InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return rerenderState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
currentHookNameInDev = 'useDebugValue';
warnInvalidHookAccess();
updateHookTypesDev();
return updateDebugValue(value, formatterFn);
},
useDeferredValue<T>(value: T, initialValue?: T): T {
currentHookNameInDev = 'useDeferredValue';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderDeferredValue(value, initialValue);
},
useTransition(): [boolean, (() => void) => void] {
currentHookNameInDev = 'useTransition';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderTransition();
},
useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
currentHookNameInDev = 'useSyncExternalStore';
warnInvalidHookAccess();
updateHookTypesDev();
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
},
useId(): string {
currentHookNameInDev = 'useId';
warnInvalidHookAccess();
updateHookTypesDev();
return updateId();
},
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh =
function useCacheRefresh() {
currentHookNameInDev = 'useCacheRefresh';
updateHookTypesDev();
return updateRefresh();
};
}
if (enableUseMemoCacheHook) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useMemoCache =
function (size: number): Array<any> {
warnInvalidHookAccess();
return useMemoCache(size);
};
}
if (enableUseEffectEventHook) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
callback: F,
): F {
currentHookNameInDev = 'useEffectEvent';
warnInvalidHookAccess();
updateHookTypesDev();
return updateEvent(callback);
};
}
if (enableFormActions && enableAsyncActions) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useFormState =
function useFormState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void] {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useOptimistic =
function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
): [S, (A) => void] {
currentHookNameInDev = 'useOptimistic';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderOptimistic(passthrough, reducer);
};
}
}