main
js 5,265 lines 172 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {
11 ReactContext,
12 StartTransitionOptions,
13 Usable,
14 Thenable,
15 RejectedThenable,
16 Awaited,
17 } from 'shared/ReactTypes';
18 import type {
19 Fiber,
20 FiberRoot,
21 Dispatcher,
22 HookType,
23 MemoCache,
24 } from './ReactInternalTypes';
25 import type {Lanes, Lane} from './ReactFiberLane';
26 import type {HookFlags} from './ReactHookEffectTags';
27 import type {Flags} from './ReactFiberFlags';
28 import type {TransitionStatus} from './ReactFiberConfig';
29 import type {ScheduledGesture} from './ReactFiberGestureScheduler';
30
31 import {
32 HostTransitionContext,
33 NotPendingTransition as NoPendingHostTransition,
34 setCurrentUpdatePriority,
35 getCurrentUpdatePriority,
36 } from './ReactFiberConfig';
37 import ReactSharedInternals from 'shared/ReactSharedInternals';
38 import {
39 enableSchedulingProfiler,
40 enableTransitionTracing,
41 enableLegacyCache,
42 disableLegacyMode,
43 enableNoCloningMemoCache,
44 enableViewTransition,
45 enableGestureTransition,
46 } from 'shared/ReactFeatureFlags';
47 import {
48 REACT_CONTEXT_TYPE,
49 REACT_RECOVERABLE_TYPE,
50 REACT_MEMO_CACHE_SENTINEL,
51 } from 'shared/ReactSymbols';
52
53 import {
54 NoMode,
55 ConcurrentMode,
56 StrictEffectsMode,
57 StrictLegacyMode,
58 } from './ReactTypeOfMode';
59 import {
60 NoLane,
61 SyncLane,
62 OffscreenLane,
63 DeferredLane,
64 NoLanes,
65 isSubsetOfLanes,
66 includesBlockingLane,
67 includesOnlyNonUrgentLanes,
68 mergeLanes,
69 removeLanes,
70 intersectLanes,
71 isTransitionLane,
72 markRootEntangled,
73 includesSomeLane,
74 isGestureRender,
75 GestureLane,
76 UpdateLanes,
77 } from './ReactFiberLane';
78 import {
79 ContinuousEventPriority,
80 higherEventPriority,
81 } from './ReactEventPriorities';
82 import {readContext, checkIfContextChanged} from './ReactFiberNewContext';
83 import {HostRoot, CacheComponent, HostComponent} from './ReactWorkTags';
84 import {
85 LayoutStatic as LayoutStaticEffect,
86 Passive as PassiveEffect,
87 PassiveStatic as PassiveStaticEffect,
88 StaticMask as StaticMaskEffect,
89 Update as UpdateEffect,
90 StoreConsistency,
91 MountLayoutDev as MountLayoutDevEffect,
92 MountPassiveDev as MountPassiveDevEffect,
93 FormReset,
94 } from './ReactFiberFlags';
95 import {
96 NoFlags as HookNoFlags,
97 HasEffect as HookHasEffect,
98 Layout as HookLayout,
99 Passive as HookPassive,
100 Insertion as HookInsertion,
101 } from './ReactHookEffectTags';
102 import {
103 getWorkInProgressRoot,
104 getWorkInProgressRootRenderLanes,
105 scheduleUpdateOnFiber,
106 requestUpdateLane,
107 requestDeferredLane,
108 markSkippedUpdateLanes,
109 isInvalidExecutionContextForEventFunction,
110 } from './ReactFiberWorkLoop';
111
112 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
113 import is from 'shared/objectIs';
114 import isArray from 'shared/isArray';
115 import {
116 markWorkInProgressReceivedUpdate,
117 checkIfWorkInProgressReceivedUpdate,
118 } from './ReactFiberBeginWork';
119 import {
120 getIsHydrating,
121 tryToClaimNextHydratableFormMarkerInstance,
122 } from './ReactFiberHydrationContext';
123 import {
124 markStateUpdateScheduled,
125 setIsStrictModeForDevtools,
126 } from './ReactFiberDevToolsHook';
127 import {
128 startUpdateTimerByLane,
129 startHostActionTimer,
130 } from './ReactProfilerTimer';
131 import {createCache} from './ReactFiberCacheComponent';
132 import {
133 createUpdate as createLegacyQueueUpdate,
134 enqueueUpdate as enqueueLegacyQueueUpdate,
135 entangleTransitions as entangleLegacyQueueTransitions,
136 } from './ReactFiberClassUpdateQueue';
137 import {
138 enqueueConcurrentHookUpdate,
139 enqueueConcurrentHookUpdateAndEagerlyBailout,
140 enqueueConcurrentRenderForLane,
141 } from './ReactFiberConcurrentUpdates';
142 import {getTreeId} from './ReactFiberTreeContext';
143 import {now} from './Scheduler';
144 import {
145 trackUsedThenable,
146 checkIfUseWrappedInTryCatch,
147 checkIfUseWasUsedBefore,
148 createThenableState,
149 SuspenseException,
150 SuspenseActionException,
151 } from './ReactFiberThenable';
152 import type {ThenableState} from './ReactFiberThenable';
153 import type {Transition} from 'react/src/ReactStartTransition';
154 import {
155 peekEntangledActionLane,
156 peekEntangledActionThenable,
157 chainThenableValue,
158 } from './ReactFiberAsyncAction';
159 import {requestTransitionLane} from './ReactFiberRootScheduler';
160 import {isCurrentTreeHidden} from './ReactFiberHiddenContext';
161 import {requestCurrentTransition} from './ReactFiberTransition';
162
163 import {callComponentInDEV} from './ReactFiberCallUserSpace';
164
165 import {scheduleGesture} from './ReactFiberGestureScheduler';
166
167 export type Update<S, A> = {
168 lane: Lane,
169 revertLane: Lane,
170 action: A,
171 hasEagerState: boolean,
172 eagerState: S | null,
173 next: Update<S, A>,
174 gesture: null | ScheduledGesture, // enableGestureTransition
175 };
176
177 export type UpdateQueue<S, A> = {
178 pending: Update<S, A> | null,
179 lanes: Lanes,
180 dispatch: (A => mixed) | null,
181 lastRenderedReducer: ((S, A) => S) | null,
182 lastRenderedState: S | null,
183 };
184
185 let didWarnAboutMismatchedHooksForComponent;
186 let didWarnUncachedGetSnapshot: void | true;
187 let didWarnAboutUseWrappedInTryCatch;
188 let didWarnAboutAsyncClientComponent;
189 let didWarnAboutUseFormState;
190 if (__DEV__) {
191 didWarnAboutMismatchedHooksForComponent = new Set<string | null>();
192 didWarnAboutUseWrappedInTryCatch = new Set<string | null>();
193 didWarnAboutAsyncClientComponent = new Set<string | null>();
194 didWarnAboutUseFormState = new Set<string | null>();
195 }
196
197 export type Hook = {
198 memoizedState: any,
199 baseState: any,
200 baseQueue: Update<any, any> | null,
201 queue: any,
202 next: Hook | null,
203 };
204
205 // The effect "instance" is a shared object that remains the same for the entire
206 // lifetime of an effect. In Rust terms, a RefCell. We use it to store the
207 // "destroy" function that is returned from an effect, because that is stateful.
208 // The field is `undefined` if the effect is unmounted, or if the effect ran
209 // but is not stateful. We don't explicitly track whether the effect is mounted
210 // or unmounted because that can be inferred by the hiddenness of the fiber in
211 // the tree, i.e. whether there is a hidden Offscreen fiber above it.
212 //
213 // It's unfortunate that this is stored on a separate object, because it adds
214 // more memory per effect instance, but it's conceptually sound. I think there's
215 // likely a better data structure we could use for effects; perhaps just one
216 // array of effect instances per fiber. But I think this is OK for now despite
217 // the additional memory and we can follow up with performance
218 // optimizations later.
219 type EffectInstance = {
220 destroy: void | (() => void),
221 };
222
223 export type Effect = {
224 tag: HookFlags,
225 inst: EffectInstance,
226 create: () => (() => void) | void,
227 deps: Array<mixed> | void | null,
228 next: Effect,
229 };
230
231 type StoreInstance<T> = {
232 value: T,
233 getSnapshot: () => T,
234 };
235
236 type StoreConsistencyCheck<T> = {
237 value: T,
238 getSnapshot: () => T,
239 };
240
241 type EventFunctionPayload<Args, Return, F: (...Array<Args>) => Return> = {
242 ref: {
243 eventFn: F,
244 impl: F,
245 },
246 nextImpl: F,
247 };
248
249 export type FunctionComponentUpdateQueue = {
250 lastEffect: Effect | null,
251 events: Array<EventFunctionPayload<any, any, any>> | null,
252 stores: Array<StoreConsistencyCheck<any>> | null,
253 memoCache: MemoCache | null,
254 };
255
256 type BasicStateAction<S> = (S => S) | S;
257
258 type Dispatch<A> = A => void;
259
260 // These are set right before calling the component.
261 let renderLanes: Lanes = NoLanes;
262 // The work-in-progress fiber. I've named it differently to distinguish it from
263 // the work-in-progress hook.
264 let currentlyRenderingFiber: Fiber = null as any;
265
266 // Hooks are stored as a linked list on the fiber's memoizedState field. The
267 // current hook list is the list that belongs to the current fiber. The
268 // work-in-progress hook list is a new list that will be added to the
269 // work-in-progress fiber.
270 let currentHook: Hook | null = null;
271 let workInProgressHook: Hook | null = null;
272
273 // Whether an update was scheduled at any point during the render phase. This
274 // does not get reset if we do another render pass; only when we're completely
275 // finished evaluating this component. This is an optimization so we know
276 // whether we need to clear render phase updates after a throw.
277 let didScheduleRenderPhaseUpdate: boolean = false;
278 // Where an update was scheduled only during the current render pass. This
279 // gets reset after each attempt.
280 // TODO: Maybe there's some way to consolidate this with
281 // `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
282 let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;
283 let shouldDoubleInvokeUserFnsInHooksDEV: boolean = false;
284 // Counts the number of useId hooks in this component.
285 let localIdCounter: number = 0;
286 // Counts number of `use`-d thenables
287 let thenableIndexCounter: number = 0;
288 let thenableState: ThenableState | null = null;
289
290 // Used for ids that are generated completely client-side (i.e. not during
291 // hydration). This counter is global, so client ids are not stable across
292 // render attempts.
293 let globalClientIdCounter: number = 0;
294
295 const RE_RENDER_LIMIT = 25;
296
297 // In DEV, this is the name of the currently executing primitive hook
298 let currentHookNameInDev: ?HookType = null;
299
300 // In DEV, this list ensures that hooks are called in the same order between renders.
301 // The list stores the order of hooks used during the initial render (mount).
302 // Subsequent renders (updates) reference this list.
303 let hookTypesDev: Array<HookType> | null = null;
304 let hookTypesUpdateIndexDev: number = -1;
305
306 // In DEV, this tracks whether currently rendering component needs to ignore
307 // the dependencies for Hooks that need them (e.g. useEffect or useMemo).
308 // When true, such Hooks will always be "remounted". Only used during hot reload.
309 let ignorePreviousDependencies: boolean = false;
310
311 function mountHookTypesDev(): void {
312 if (__DEV__) {
313 const hookName = currentHookNameInDev as any as HookType;
314
315 if (hookTypesDev === null) {
316 hookTypesDev = [hookName];
317 } else {
318 hookTypesDev.push(hookName);
319 }
320 }
321 }
322
323 function updateHookTypesDev(): void {
324 if (__DEV__) {
325 const hookName = currentHookNameInDev as any as HookType;
326
327 if (hookTypesDev !== null) {
328 hookTypesUpdateIndexDev++;
329 if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
330 warnOnHookMismatchInDev(hookName);
331 }
332 }
333 }
334 }
335
336 function checkDepsAreArrayDev(deps: mixed): void {
337 if (__DEV__) {
338 if (deps !== undefined && deps !== null && !isArray(deps)) {
339 // Verify deps, but only on mount to avoid extra checks.
340 // It's unlikely their type would change as usually you define them inline.
341 console.error(
342 '%s received a final argument that is not an array (instead, received `%s`). When ' +
343 'specified, the final argument must be an array.',
344 currentHookNameInDev,
345 typeof deps,
346 );
347 }
348 }
349 }
350
351 function warnOnHookMismatchInDev(currentHookName: HookType): void {
352 if (__DEV__) {
353 const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
354 if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
355 didWarnAboutMismatchedHooksForComponent.add(componentName);
356
357 if (hookTypesDev !== null) {
358 let table = '';
359
360 const secondColumnStart = 30;
361
362 for (let i = 0; i <= (hookTypesUpdateIndexDev as any as number); i++) {
363 const oldHookName = hookTypesDev[i];
364 const newHookName =
365 i === (hookTypesUpdateIndexDev as any as number)
366 ? currentHookName
367 : oldHookName;
368
369 let row = `${i + 1}. ${oldHookName}`;
370
371 // Extra space so second column lines up
372 // lol @ IE not supporting String#repeat
373 while (row.length < secondColumnStart) {
374 row += ' ';
375 }
376
377 row += newHookName + '\n';
378
379 table += row;
380 }
381
382 console.error(
383 'React has detected a change in the order of Hooks called by %s. ' +
384 'This will lead to bugs and errors if not fixed. ' +
385 'For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
386 ' Previous render Next render\n' +
387 ' ------------------------------------------------------\n' +
388 '%s' +
389 ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
390 componentName,
391 table,
392 );
393 }
394 }
395 }
396 }
397
398 function warnOnUseFormStateInDev(): void {
399 if (__DEV__) {
400 const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
401 if (!didWarnAboutUseFormState.has(componentName)) {
402 didWarnAboutUseFormState.add(componentName);
403
404 console.error(
405 'ReactDOM.useFormState has been renamed to React.useActionState. ' +
406 'Please update %s to use React.useActionState.',
407 componentName,
408 );
409 }
410 }
411 }
412
413 function warnIfAsyncClientComponent(Component: Function) {
414 if (__DEV__) {
415 // This dev-only check only works for detecting native async functions,
416 // not transpiled ones. There's also a prod check that we use to prevent
417 // async client components from crashing the app; the prod one works even
418 // for transpiled async functions. Neither mechanism is completely
419 // bulletproof but together they cover the most common cases.
420 const isAsyncFunction =
421 // $FlowFixMe[method-unbinding]
422 Object.prototype.toString.call(Component) === '[object AsyncFunction]' ||
423 // $FlowFixMe[method-unbinding]
424 Object.prototype.toString.call(Component) ===
425 '[object AsyncGeneratorFunction]';
426 if (isAsyncFunction) {
427 // Encountered an async Client Component. This is not yet supported.
428 const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
429 if (!didWarnAboutAsyncClientComponent.has(componentName)) {
430 didWarnAboutAsyncClientComponent.add(componentName);
431 console.error(
432 '%s is an async Client Component. ' +
433 'Only Server Components can be async at the moment. This error is often caused by accidentally ' +
434 "adding `'use client'` to a module that was originally written " +
435 'for the server.',
436 componentName === null
437 ? 'An unknown Component'
438 : `<${componentName}>`,
439 );
440 }
441 }
442 }
443 }
444
445 function throwInvalidHookError() {
446 throw new Error(
447 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
448 ' one of the following reasons:\n' +
449 '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
450 '2. You might be breaking the Rules of Hooks\n' +
451 '3. You might have more than one copy of React in the same app\n' +
452 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
453 );
454 }
455
456 function areHookInputsEqual(
457 nextDeps: Array<mixed>,
458 prevDeps: Array<mixed> | null,
459 ): boolean {
460 if (__DEV__) {
461 if (ignorePreviousDependencies) {
462 // Only true when this component is being hot reloaded.
463 return false;
464 }
465 }
466
467 if (prevDeps === null) {
468 if (__DEV__) {
469 console.error(
470 '%s received a final argument during this render, but not during ' +
471 'the previous render. Even though the final argument is optional, ' +
472 'its type cannot change between renders.',
473 currentHookNameInDev,
474 );
475 }
476 return false;
477 }
478
479 if (__DEV__) {
480 // Don't bother comparing lengths in prod because these arrays should be
481 // passed inline.
482 if (nextDeps.length !== prevDeps.length) {
483 console.error(
484 'The final argument passed to %s changed size between renders. The ' +
485 'order and size of this array must remain constant.\n\n' +
486 'Previous: %s\n' +
487 'Incoming: %s',
488 currentHookNameInDev,
489 `[${prevDeps.join(', ')}]`,
490 `[${nextDeps.join(', ')}]`,
491 );
492 }
493 }
494 // $FlowFixMe[incompatible-use] found when upgrading Flow
495 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
496 // $FlowFixMe[incompatible-use] found when upgrading Flow
497 if (is(nextDeps[i], prevDeps[i])) {
498 continue;
499 }
500 return false;
501 }
502 return true;
503 }
504
505 export function renderWithHooks<Props, SecondArg>(
506 current: Fiber | null,
507 workInProgress: Fiber,
508 Component: (p: Props, arg: SecondArg) => any,
509 props: Props,
510 secondArg: SecondArg,
511 nextRenderLanes: Lanes,
512 ): any {
513 renderLanes = nextRenderLanes;
514 currentlyRenderingFiber = workInProgress;
515
516 if (__DEV__) {
517 hookTypesDev =
518 current !== null
519 ? (current._debugHookTypes as any as Array<HookType>)
520 : null;
521 hookTypesUpdateIndexDev = -1;
522 // Used for hot reloading:
523 ignorePreviousDependencies =
524 current !== null && current.type !== workInProgress.type;
525
526 warnIfAsyncClientComponent(Component);
527 }
528
529 workInProgress.memoizedState = null;
530 workInProgress.updateQueue = null;
531 workInProgress.lanes = NoLanes;
532
533 // The following should have already been reset
534 // currentHook = null;
535 // workInProgressHook = null;
536
537 // didScheduleRenderPhaseUpdate = false;
538 // localIdCounter = 0;
539 // thenableIndexCounter = 0;
540 // thenableState = null;
541
542 // TODO Warn if no hooks are used at all during mount, then some are used during update.
543 // Currently we will identify the update render as a mount because memoizedState === null.
544 // This is tricky because it's valid for certain types of components (e.g. React.lazy)
545
546 // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
547 // Non-stateful hooks (e.g. context) don't get added to memoizedState,
548 // so memoizedState would be null during updates and mounts.
549 if (__DEV__) {
550 if (current !== null && current.memoizedState !== null) {
551 ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
552 } else if (hookTypesDev !== null) {
553 // This dispatcher handles an edge case where a component is updating,
554 // but no stateful hooks have been used.
555 // We want to match the production code behavior (which will use HooksDispatcherOnMount),
556 // but with the extra DEV validation to ensure hooks ordering hasn't changed.
557 // This dispatcher does that.
558 ReactSharedInternals.H = HooksDispatcherOnMountWithHookTypesInDEV;
559 } else {
560 ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
561 }
562 } else {
563 ReactSharedInternals.H =
564 current === null || current.memoizedState === null
565 ? HooksDispatcherOnMount
566 : HooksDispatcherOnUpdate;
567 }
568
569 // In Strict Mode, during development, user functions are double invoked to
570 // help detect side effects. The logic for how this is implemented for in
571 // hook components is a bit complex so let's break it down.
572 //
573 // We will invoke the entire component function twice. However, during the
574 // second invocation of the component, the hook state from the first
575 // invocation will be reused. That means things like `useMemo` functions won't
576 // run again, because the deps will match and the memoized result will
577 // be reused.
578 //
579 // We want memoized functions to run twice, too, so account for this, user
580 // functions are double invoked during the *first* invocation of the component
581 // function, and are *not* double invoked during the second incovation:
582 //
583 // - First execution of component function: user functions are double invoked
584 // - Second execution of component function (in Strict Mode, during
585 // development): user functions are not double invoked.
586 //
587 // This is intentional for a few reasons; most importantly, it's because of
588 // how `use` works when something suspends: it reuses the promise that was
589 // passed during the first attempt. This is itself a form of memoization.
590 // We need to be able to memoize the reactive inputs to the `use` call using
591 // a hook (i.e. `useMemo`), which means, the reactive inputs to `use` must
592 // come from the same component invocation as the output.
593 //
594 // There are plenty of tests to ensure this behavior is correct.
595 const shouldDoubleRenderDEV =
596 __DEV__ && (workInProgress.mode & StrictLegacyMode) !== NoMode;
597
598 shouldDoubleInvokeUserFnsInHooksDEV = shouldDoubleRenderDEV;
599 let children = __DEV__
600 ? callComponentInDEV(Component, props, secondArg)
601 : Component(props, secondArg);
602 shouldDoubleInvokeUserFnsInHooksDEV = false;
603
604 // Check if there was a render phase update
605 if (didScheduleRenderPhaseUpdateDuringThisPass) {
606 // Keep rendering until the component stabilizes (there are no more render
607 // phase updates).
608 children = renderWithHooksAgain(
609 workInProgress,
610 Component,
611 props,
612 secondArg,
613 );
614 }
615
616 if (shouldDoubleRenderDEV) {
617 // In development, components are invoked twice to help detect side effects.
618 setIsStrictModeForDevtools(true);
619 try {
620 children = renderWithHooksAgain(
621 workInProgress,
622 Component,
623 props,
624 secondArg,
625 );
626 } finally {
627 setIsStrictModeForDevtools(false);
628 }
629 }
630
631 finishRenderingHooks(current, workInProgress, Component);
632
633 return children;
634 }
635
636 function finishRenderingHooks<Props, SecondArg>(
637 current: Fiber | null,
638 workInProgress: Fiber,
639 Component: (p: Props, arg: SecondArg) => any,
640 ): void {
641 if (__DEV__) {
642 workInProgress._debugHookTypes = hookTypesDev;
643 // Stash the thenable state for use by DevTools.
644 if (workInProgress.dependencies === null) {
645 if (thenableState !== null) {
646 workInProgress.dependencies = {
647 lanes: NoLanes,
648 firstContext: null,
649 _debugThenableState: thenableState,
650 };
651 }
652 } else {
653 workInProgress.dependencies._debugThenableState = thenableState;
654 }
655 checkIfUseWasUsedBefore(workInProgress, thenableState);
656 }
657
658 // We can assume the previous dispatcher is always this one, since we set it
659 // at the beginning of the render phase and there's no re-entrance.
660 ReactSharedInternals.H = ContextOnlyDispatcher;
661
662 // This check uses currentHook so that it works the same in DEV and prod bundles.
663 // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
664 const didRenderTooFewHooks =
665 currentHook !== null && currentHook.next !== null;
666
667 renderLanes = NoLanes;
668 currentlyRenderingFiber = null as any;
669
670 currentHook = null;
671 workInProgressHook = null;
672
673 if (__DEV__) {
674 currentHookNameInDev = null;
675 hookTypesDev = null;
676 hookTypesUpdateIndexDev = -1;
677
678 // Confirm that a static flag was not added or removed since the last
679 // render. If this fires, it suggests that we incorrectly reset the static
680 // flags in some other part of the codebase. This has happened before, for
681 // example, in the SuspenseList implementation.
682 if (
683 current !== null &&
684 (current.flags & StaticMaskEffect) !==
685 (workInProgress.flags & StaticMaskEffect) &&
686 // Disable this warning in legacy mode, because legacy Suspense is weird
687 // and creates false positives. To make this work in legacy mode, we'd
688 // need to mark fibers that commit in an incomplete state, somehow. For
689 // now I'll disable the warning that most of the bugs that would trigger
690 // it are either exclusive to concurrent mode or exist in both.
691 (disableLegacyMode || (current.mode & ConcurrentMode) !== NoMode)
692 ) {
693 console.error(
694 'Internal React error: Expected static flag was missing. Please ' +
695 'notify the React team.',
696 );
697 }
698 }
699
700 didScheduleRenderPhaseUpdate = false;
701 // This is reset by checkDidRenderIdHook
702 // localIdCounter = 0;
703
704 thenableIndexCounter = 0;
705 thenableState = null;
706
707 if (didRenderTooFewHooks) {
708 throw new Error(
709 'Rendered fewer hooks than expected. This may be caused by an accidental ' +
710 'early return statement.',
711 );
712 }
713
714 if (current !== null) {
715 if (!checkIfWorkInProgressReceivedUpdate()) {
716 // If there were no changes to props or state, we need to check if there
717 // was a context change. We didn't already do this because there's no
718 // 1:1 correspondence between dependencies and hooks. Although, because
719 // there almost always is in the common case (`readContext` is an
720 // internal API), we could compare in there. OTOH, we only hit this case
721 // if everything else bails out, so on the whole it might be better to
722 // keep the comparison out of the common path.
723 const currentDependencies = current.dependencies;
724 if (
725 currentDependencies !== null &&
726 checkIfContextChanged(currentDependencies)
727 ) {
728 markWorkInProgressReceivedUpdate();
729 }
730 }
731 }
732
733 if (__DEV__) {
734 if (checkIfUseWrappedInTryCatch()) {
735 const componentName =
736 getComponentNameFromFiber(workInProgress) || 'Unknown';
737 if (
738 !didWarnAboutUseWrappedInTryCatch.has(componentName) &&
739 // This warning also fires if you suspend with `use` inside an
740 // async component. Since we warn for that above, we'll silence this
741 // second warning by checking here.
742 !didWarnAboutAsyncClientComponent.has(componentName)
743 ) {
744 didWarnAboutUseWrappedInTryCatch.add(componentName);
745 console.error(
746 '`use` was called from inside a try/catch block. This is not allowed ' +
747 'and can lead to unexpected behavior. To handle errors triggered ' +
748 'by `use`, wrap your component in a error boundary.',
749 );
750 }
751 }
752 }
753 }
754
755 export function replaySuspendedComponentWithHooks<Props, SecondArg>(
756 current: Fiber | null,
757 workInProgress: Fiber,
758 Component: (p: Props, arg: SecondArg) => any,
759 props: Props,
760 secondArg: SecondArg,
761 ): any {
762 // This function is used to replay a component that previously suspended,
763 // after its data resolves.
764 //
765 // It's a simplified version of renderWithHooks, but it doesn't need to do
766 // most of the set up work because they weren't reset when we suspended; they
767 // only get reset when the component either completes (finishRenderingHooks)
768 // or unwinds (resetHooksOnUnwind).
769 if (__DEV__) {
770 hookTypesUpdateIndexDev = -1;
771 // Used for hot reloading:
772 ignorePreviousDependencies =
773 current !== null && current.type !== workInProgress.type;
774 }
775 // renderWithHooks only resets the updateQueue but does not clear it, since
776 // it needs to work for both this case (suspense replay) as well as for double
777 // renders in dev and setState-in-render. However, for the suspense replay case
778 // we need to reset the updateQueue to correctly handle unmount effects, so we
779 // clear the queue here
780 workInProgress.updateQueue = null;
781 const children = renderWithHooksAgain(
782 workInProgress,
783 Component,
784 props,
785 secondArg,
786 );
787 finishRenderingHooks(current, workInProgress, Component);
788 return children;
789 }
790
791 function renderWithHooksAgain<Props, SecondArg>(
792 workInProgress: Fiber,
793 Component: (p: Props, arg: SecondArg) => any,
794 props: Props,
795 secondArg: SecondArg,
796 ): any {
797 // This is used to perform another render pass. It's used when setState is
798 // called during render, and for double invoking components in Strict Mode
799 // during development.
800 //
801 // The state from the previous pass is reused whenever possible. So, state
802 // updates that were already processed are not processed again, and memoized
803 // functions (`useMemo`) are not invoked again.
804 //
805 // Keep rendering in a loop for as long as render phase updates continue to
806 // be scheduled. Use a counter to prevent infinite loops.
807
808 currentlyRenderingFiber = workInProgress;
809
810 let numberOfReRenders: number = 0;
811 let children;
812 do {
813 if (didScheduleRenderPhaseUpdateDuringThisPass) {
814 // It's possible that a use() value depended on a state that was updated in
815 // this rerender, so we need to watch for different thenables this time.
816 thenableState = null;
817 }
818 thenableIndexCounter = 0;
819 didScheduleRenderPhaseUpdateDuringThisPass = false;
820
821 if (numberOfReRenders >= RE_RENDER_LIMIT) {
822 throw new Error(
823 'Too many re-renders. React limits the number of renders to prevent ' +
824 'an infinite loop.',
825 );
826 }
827
828 numberOfReRenders += 1;
829 if (__DEV__) {
830 // Even when hot reloading, allow dependencies to stabilize
831 // after first render to prevent infinite render phase updates.
832 ignorePreviousDependencies = false;
833 }
834
835 // Start over from the beginning of the list
836 currentHook = null;
837 workInProgressHook = null;
838
839 if (workInProgress.updateQueue != null) {
840 resetFunctionComponentUpdateQueue(workInProgress.updateQueue as any);
841 }
842
843 if (__DEV__) {
844 // Also validate hook order for cascading updates.
845 hookTypesUpdateIndexDev = -1;
846 }
847
848 ReactSharedInternals.H = __DEV__
849 ? HooksDispatcherOnRerenderInDEV
850 : HooksDispatcherOnRerender;
851
852 children = __DEV__
853 ? callComponentInDEV(Component, props, secondArg)
854 : Component(props, secondArg);
855 } while (didScheduleRenderPhaseUpdateDuringThisPass);
856 return children;
857 }
858
859 export function renderTransitionAwareHostComponentWithHooks(
860 current: Fiber | null,
861 workInProgress: Fiber,
862 lanes: Lanes,
863 ): TransitionStatus {
864 return renderWithHooks(
865 current,
866 workInProgress,
867 TransitionAwareHostComponent,
868 null,
869 null,
870 lanes,
871 );
872 }
873
874 export function TransitionAwareHostComponent(): TransitionStatus {
875 const dispatcher: any = ReactSharedInternals.H;
876 const [maybeThenable] = dispatcher.useState();
877 let nextState;
878 if (typeof maybeThenable.then === 'function') {
879 const thenable: Thenable<TransitionStatus> = maybeThenable as any;
880 nextState = useThenable(thenable);
881 } else {
882 const status: TransitionStatus = maybeThenable;
883 nextState = status;
884 }
885
886 // The "reset state" is an object. If it changes, that means something
887 // requested that we reset the form.
888 const [nextResetState] = dispatcher.useState();
889 const prevResetState =
890 currentHook !== null ? currentHook.memoizedState : null;
891 if (prevResetState !== nextResetState) {
892 // Schedule a form reset
893 currentlyRenderingFiber.flags |= FormReset;
894 }
895
896 return nextState;
897 }
898
899 export function checkDidRenderIdHook(): boolean {
900 // This should be called immediately after every renderWithHooks call.
901 // Conceptually, it's part of the return value of renderWithHooks; it's only a
902 // separate function to avoid using an array tuple.
903 const didRenderIdHook = localIdCounter !== 0;
904 localIdCounter = 0;
905 return didRenderIdHook;
906 }
907
908 export function bailoutHooks(
909 current: Fiber,
910 workInProgress: Fiber,
911 lanes: Lanes,
912 ): void {
913 workInProgress.updateQueue = current.updateQueue;
914 // TODO: Don't need to reset the flags here, because they're reset in the
915 // complete phase (bubbleProperties).
916 if (__DEV__ && (workInProgress.mode & StrictEffectsMode) !== NoMode) {
917 workInProgress.flags &= ~(
918 MountPassiveDevEffect |
919 MountLayoutDevEffect |
920 PassiveEffect |
921 UpdateEffect
922 );
923 } else {
924 workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
925 }
926 current.lanes = removeLanes(current.lanes, lanes);
927 }
928
929 export function resetHooksAfterThrow(): void {
930 // This is called immediaetly after a throw. It shouldn't reset the entire
931 // module state, because the work loop might decide to replay the component
932 // again without rewinding.
933 //
934 // It should only reset things like the current dispatcher, to prevent hooks
935 // from being called outside of a component.
936 currentlyRenderingFiber = null as any;
937
938 // We can assume the previous dispatcher is always this one, since we set it
939 // at the beginning of the render phase and there's no re-entrance.
940 ReactSharedInternals.H = ContextOnlyDispatcher;
941 }
942
943 export function resetHooksOnUnwind(workInProgress: Fiber): void {
944 if (didScheduleRenderPhaseUpdate) {
945 // There were render phase updates. These are only valid for this render
946 // phase, which we are now aborting. Remove the updates from the queues so
947 // they do not persist to the next render. Do not remove updates from hooks
948 // that weren't processed.
949 //
950 // Only reset the updates from the queue if it has a clone. If it does
951 // not have a clone, that means it wasn't processed, and the updates were
952 // scheduled before we entered the render phase.
953 let hook: Hook | null = workInProgress.memoizedState;
954 while (hook !== null) {
955 const queue = hook.queue;
956 if (queue !== null) {
957 queue.pending = null;
958 }
959 hook = hook.next;
960 }
961 didScheduleRenderPhaseUpdate = false;
962 }
963
964 renderLanes = NoLanes;
965 currentlyRenderingFiber = null as any;
966
967 currentHook = null;
968 workInProgressHook = null;
969
970 if (__DEV__) {
971 hookTypesDev = null;
972 hookTypesUpdateIndexDev = -1;
973
974 currentHookNameInDev = null;
975 }
976
977 didScheduleRenderPhaseUpdateDuringThisPass = false;
978 localIdCounter = 0;
979 thenableIndexCounter = 0;
980 thenableState = null;
981 }
982
983 function mountWorkInProgressHook(): Hook {
984 const hook: Hook = {
985 memoizedState: null,
986
987 baseState: null,
988 baseQueue: null,
989 queue: null,
990
991 next: null,
992 };
993
994 if (workInProgressHook === null) {
995 // This is the first hook in the list
996 currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
997 } else {
998 // Append to the end of the list
999 workInProgressHook = workInProgressHook.next = hook;
1000 }
1001 return workInProgressHook;
1002 }
1003
1004 function updateWorkInProgressHook(): Hook {
1005 // This function is used both for updates and for re-renders triggered by a
1006 // render phase update. It assumes there is either a current hook we can
1007 // clone, or a work-in-progress hook from a previous render pass that we can
1008 // use as a base.
1009 let nextCurrentHook: null | Hook;
1010 if (currentHook === null) {
1011 const current = currentlyRenderingFiber.alternate;
1012 if (current !== null) {
1013 nextCurrentHook = current.memoizedState;
1014 } else {
1015 nextCurrentHook = null;
1016 }
1017 } else {
1018 nextCurrentHook = currentHook.next;
1019 }
1020
1021 let nextWorkInProgressHook: null | Hook;
1022 if (workInProgressHook === null) {
1023 nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
1024 } else {
1025 nextWorkInProgressHook = workInProgressHook.next;
1026 }
1027
1028 if (nextWorkInProgressHook !== null) {
1029 // There's already a work-in-progress. Reuse it.
1030 workInProgressHook = nextWorkInProgressHook;
1031 nextWorkInProgressHook = workInProgressHook.next;
1032
1033 currentHook = nextCurrentHook;
1034 } else {
1035 // Clone from the current hook.
1036
1037 if (nextCurrentHook === null) {
1038 const currentFiber = currentlyRenderingFiber.alternate;
1039 if (currentFiber === null) {
1040 // This is the initial render. This branch is reached when the component
1041 // suspends, resumes, then renders an additional hook.
1042 // Should never be reached because we should switch to the mount dispatcher first.
1043 throw new Error(
1044 'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
1045 );
1046 } else {
1047 // This is an update. We should always have a current hook.
1048 throw new Error('Rendered more hooks than during the previous render.');
1049 }
1050 }
1051
1052 currentHook = nextCurrentHook;
1053
1054 const newHook: Hook = {
1055 memoizedState: currentHook.memoizedState,
1056
1057 baseState: currentHook.baseState,
1058 baseQueue: currentHook.baseQueue,
1059 queue: currentHook.queue,
1060
1061 next: null,
1062 };
1063
1064 if (workInProgressHook === null) {
1065 // This is the first hook in the list.
1066 currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
1067 } else {
1068 // Append to the end of the list.
1069 workInProgressHook = workInProgressHook.next = newHook;
1070 }
1071 }
1072 return workInProgressHook;
1073 }
1074
1075 function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
1076 return {
1077 lastEffect: null,
1078 events: null,
1079 stores: null,
1080 memoCache: null,
1081 };
1082 }
1083
1084 function resetFunctionComponentUpdateQueue(
1085 updateQueue: FunctionComponentUpdateQueue,
1086 ): void {
1087 updateQueue.lastEffect = null;
1088 updateQueue.events = null;
1089 updateQueue.stores = null;
1090 if (updateQueue.memoCache != null) {
1091 // NOTE: this function intentionally does not reset memoCache data. We reuse updateQueue for the memo
1092 // cache to avoid increasing the size of fibers that don't need a cache, but we don't want to reset
1093 // the cache when other properties are reset.
1094 updateQueue.memoCache.index = 0;
1095 }
1096 }
1097
1098 function useThenable<T>(thenable: Thenable<T>): T {
1099 // Track the position of the thenable within this fiber.
1100 const index = thenableIndexCounter;
1101 thenableIndexCounter += 1;
1102 if (thenableState === null) {
1103 thenableState = createThenableState();
1104 }
1105 const result = trackUsedThenable(
1106 thenableState,
1107 thenable,
1108 index,
1109 __DEV__ ? currentlyRenderingFiber : null,
1110 );
1111
1112 // When something suspends with `use`, we replay the component with the
1113 // "re-render" dispatcher instead of the "mount" or "update" dispatcher.
1114 //
1115 // But if there are additional hooks that occur after the `use` invocation
1116 // that suspended, they wouldn't have been processed during the previous
1117 // attempt. So after we invoke `use` again, we may need to switch from the
1118 // "re-render" dispatcher back to the "mount" or "update" dispatcher. That's
1119 // what the following logic accounts for.
1120 //
1121 // TODO: Theoretically this logic only needs to go into the rerender
1122 // dispatcher. Could optimize, but probably not be worth it.
1123
1124 // This is the same logic as in updateWorkInProgressHook.
1125 const workInProgressFiber = currentlyRenderingFiber;
1126 const nextWorkInProgressHook =
1127 workInProgressHook === null
1128 ? // We're at the beginning of the list, so read from the first hook from
1129 // the fiber.
1130 workInProgressFiber.memoizedState
1131 : workInProgressHook.next;
1132
1133 if (nextWorkInProgressHook !== null) {
1134 // There are still hooks remaining from the previous attempt.
1135 } else {
1136 // There are no remaining hooks from the previous attempt. We're no longer
1137 // in "re-render" mode. Switch to the normal mount or update dispatcher.
1138 //
1139 // This is the same as the logic in renderWithHooks, except we don't bother
1140 // to track the hook types debug information in this case (sufficient to
1141 // only do that when nothing suspends).
1142 const currentFiber = workInProgressFiber.alternate;
1143 if (__DEV__) {
1144 if (currentFiber !== null && currentFiber.memoizedState !== null) {
1145 ReactSharedInternals.H = HooksDispatcherOnUpdateInDEV;
1146 } else {
1147 ReactSharedInternals.H = HooksDispatcherOnMountInDEV;
1148 }
1149 } else {
1150 ReactSharedInternals.H =
1151 currentFiber === null || currentFiber.memoizedState === null
1152 ? HooksDispatcherOnMount
1153 : HooksDispatcherOnUpdate;
1154 }
1155 }
1156 return result;
1157 }
1158
1159 function use<T>(usable: Usable<T>): T {
1160 // $FlowFixMe[invalid-compare]
1161 if (usable !== null && typeof usable === 'object') {
1162 // $FlowFixMe[method-unbinding]
1163 if (typeof usable.then === 'function') {
1164 // This is a thenable.
1165 const thenable: Thenable<T> = usable as any;
1166 return useThenable(thenable);
1167 } else if (usable.$$typeof === REACT_RECOVERABLE_TYPE) {
1168 // Fiber is the final renderer, so there is no downstream host that
1169 // needs to recover this subtree. Continue rendering through it.
1170 return undefined as any;
1171 } else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
1172 const context: ReactContext<T> = usable as any;
1173 return readContext(context);
1174 }
1175 }
1176
1177 // eslint-disable-next-line react-internal/safe-string-coercion
1178 throw new Error('An unsupported type was passed to use(): ' + String(usable));
1179 }
1180
1181 function useMemoCache(size: number): Array<mixed> {
1182 let memoCache = null;
1183 // Fast-path, load memo cache from wip fiber if already prepared
1184 let updateQueue: FunctionComponentUpdateQueue | null =
1185 currentlyRenderingFiber.updateQueue as any;
1186 if (updateQueue !== null) {
1187 memoCache = updateQueue.memoCache;
1188 }
1189 // Otherwise clone from the current fiber
1190 if (memoCache == null) {
1191 const current: Fiber | null = currentlyRenderingFiber.alternate;
1192 if (current !== null) {
1193 const currentUpdateQueue: FunctionComponentUpdateQueue | null =
1194 current.updateQueue as any;
1195 if (currentUpdateQueue !== null) {
1196 const currentMemoCache: ?MemoCache = currentUpdateQueue.memoCache;
1197 if (currentMemoCache != null) {
1198 memoCache = {
1199 // When enableNoCloningMemoCache is enabled, instead of treating the
1200 // cache as copy-on-write, like we do with fibers, we share the same
1201 // cache instance across all render attempts, even if the component
1202 // is interrupted before it commits.
1203 //
1204 // If an update is interrupted, either because it suspended or
1205 // because of another update, we can reuse the memoized computations
1206 // from the previous attempt. We can do this because the React
1207 // Compiler performs atomic writes to the memo cache, i.e. it will
1208 // not record the inputs to a memoization without also recording its
1209 // output.
1210 //
1211 // This gives us a form of "resuming" within components and hooks.
1212 //
1213 // This only works when updating a component that already mounted.
1214 // It has no impact during initial render, because the memo cache is
1215 // stored on the fiber, and since we have not implemented resuming
1216 // for fibers, it's always a fresh memo cache, anyway.
1217 //
1218 // However, this alone is pretty useful — it happens whenever you
1219 // update the UI with fresh data after a mutation/action, which is
1220 // extremely common in a Suspense-driven (e.g. RSC or Relay) app.
1221 data: enableNoCloningMemoCache
1222 ? currentMemoCache.data
1223 : // Clone the memo cache before each render (copy-on-write)
1224 currentMemoCache.data.map(array => array.slice()),
1225 index: 0 as number,
1226 };
1227 }
1228 }
1229 }
1230 }
1231 // Finally fall back to allocating a fresh instance of the cache
1232 if (memoCache == null) {
1233 memoCache = {
1234 data: [],
1235 index: 0 as number,
1236 };
1237 }
1238 if (updateQueue === null) {
1239 updateQueue = createFunctionComponentUpdateQueue();
1240 currentlyRenderingFiber.updateQueue = updateQueue;
1241 }
1242 updateQueue.memoCache = memoCache;
1243
1244 let data = memoCache.data[memoCache.index];
1245 if (data === undefined || (__DEV__ && ignorePreviousDependencies)) {
1246 data = memoCache.data[memoCache.index] = new Array(size);
1247 for (let i = 0; i < size; i++) {
1248 data[i] = REACT_MEMO_CACHE_SENTINEL;
1249 }
1250 } else if (data.length !== size) {
1251 // TODO: consider warning or throwing here
1252 if (__DEV__) {
1253 console.error(
1254 'Expected a constant size argument for each invocation of useMemoCache. ' +
1255 'The previous cache was allocated with size %s but size %s was requested.',
1256 data.length,
1257 size,
1258 );
1259 }
1260 }
1261 memoCache.index++;
1262 return data;
1263 }
1264
1265 function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
1266 // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1267 return typeof action === 'function' ? action(state) : action;
1268 }
1269
1270 function mountReducer<S, I, A>(
1271 reducer: (S, A) => S,
1272 initialArg: I,
1273 init?: I => S,
1274 ): [S, Dispatch<A>] {
1275 const hook = mountWorkInProgressHook();
1276 let initialState;
1277 if (init !== undefined) {
1278 initialState = init(initialArg);
1279 if (shouldDoubleInvokeUserFnsInHooksDEV) {
1280 setIsStrictModeForDevtools(true);
1281 try {
1282 init(initialArg);
1283 } finally {
1284 setIsStrictModeForDevtools(false);
1285 }
1286 }
1287 } else {
1288 initialState = initialArg as any as S;
1289 }
1290 hook.memoizedState = hook.baseState = initialState;
1291 const queue: UpdateQueue<S, A> = {
1292 pending: null,
1293 lanes: NoLanes,
1294 dispatch: null,
1295 lastRenderedReducer: reducer,
1296 lastRenderedState: initialState as any,
1297 };
1298 hook.queue = queue;
1299 const dispatch: Dispatch<A> = (queue.dispatch = dispatchReducerAction.bind(
1300 null,
1301 currentlyRenderingFiber,
1302 queue,
1303 ) as any);
1304 return [hook.memoizedState, dispatch];
1305 }
1306
1307 function updateReducer<S, I, A>(
1308 reducer: (S, A) => S,
1309 initialArg: I,
1310 init?: I => S,
1311 ): [S, Dispatch<A>] {
1312 const hook = updateWorkInProgressHook();
1313 return updateReducerImpl(hook, currentHook as any as Hook, reducer);
1314 }
1315
1316 function updateReducerImpl<S, A>(
1317 hook: Hook,
1318 current: Hook,
1319 reducer: (S, A) => S,
1320 ): [S, Dispatch<A>] {
1321 const queue = hook.queue;
1322
1323 if (queue === null) {
1324 throw new Error(
1325 'Should have a queue. You are likely calling Hooks conditionally, ' +
1326 'which is not allowed. (https://react.dev/link/invalid-hook-call)',
1327 );
1328 }
1329
1330 queue.lastRenderedReducer = reducer;
1331
1332 // The last rebase update that is NOT part of the base state.
1333 let baseQueue = hook.baseQueue;
1334
1335 // The last pending update that hasn't been processed yet.
1336 const pendingQueue = queue.pending;
1337 if (pendingQueue !== null) {
1338 // We have new updates that haven't been processed yet.
1339 // We'll add them to the base queue.
1340 if (baseQueue !== null) {
1341 // Merge the pending queue and the base queue.
1342 const baseFirst = baseQueue.next;
1343 const pendingFirst = pendingQueue.next;
1344 baseQueue.next = pendingFirst;
1345 pendingQueue.next = baseFirst;
1346 }
1347 if (__DEV__) {
1348 if (current.baseQueue !== baseQueue) {
1349 // Internal invariant that should never happen, but feasibly could in
1350 // the future if we implement resuming, or some form of that.
1351 console.error(
1352 'Internal error: Expected work-in-progress queue to be a clone. ' +
1353 'This is a bug in React.',
1354 );
1355 }
1356 }
1357 current.baseQueue = baseQueue = pendingQueue;
1358 queue.pending = null;
1359 }
1360
1361 const baseState = hook.baseState;
1362 if (baseQueue === null) {
1363 // If there are no pending updates, then the memoized state should be the
1364 // same as the base state. Currently these only diverge in the case of
1365 // useOptimistic, because useOptimistic accepts a new baseState on
1366 // every render.
1367 hook.memoizedState = baseState;
1368 // We don't need to call markWorkInProgressReceivedUpdate because
1369 // baseState is derived from other reactive values.
1370 } else {
1371 // We have a queue to process.
1372 const first = baseQueue.next;
1373 let newState = baseState;
1374
1375 let newBaseState = null;
1376 let newBaseQueueFirst = null;
1377 let newBaseQueueLast: Update<S, A> | null = null;
1378 let update = first;
1379 let didReadFromEntangledAsyncAction = false;
1380 do {
1381 // An extra OffscreenLane bit is added to updates that were made to
1382 // a hidden tree, so that we can distinguish them from updates that were
1383 // already there when the tree was hidden.
1384 const updateLane = removeLanes(update.lane, OffscreenLane);
1385 const isHiddenUpdate = updateLane !== update.lane;
1386
1387 // Check if this update was made while the tree was hidden. If so, then
1388 // it's not a "base" update and we should disregard the extra base lanes
1389 // that were added to renderLanes when we entered the Offscreen tree.
1390 let shouldSkipUpdate = isHiddenUpdate
1391 ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
1392 : !isSubsetOfLanes(renderLanes, updateLane);
1393
1394 if (enableGestureTransition && updateLane === GestureLane) {
1395 // This is a gesture optimistic update. It should only be considered as part of the
1396 // rendered state while rendering the gesture lane and if the rendering the associated
1397 // ScheduledGesture.
1398 const scheduledGesture = update.gesture;
1399 if (scheduledGesture !== null) {
1400 if (scheduledGesture.count === 0 && !scheduledGesture.committing) {
1401 // This gesture has already been cancelled. We can clean up this update.
1402 update = update.next;
1403 continue;
1404 } else if (!isGestureRender(renderLanes)) {
1405 shouldSkipUpdate = true;
1406 } else {
1407 const root: FiberRoot | null = getWorkInProgressRoot();
1408 if (root === null) {
1409 throw new Error(
1410 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1411 );
1412 }
1413 // We assume that the currently rendering gesture is the one first in the queue.
1414 shouldSkipUpdate = root.pendingGestures !== scheduledGesture;
1415 }
1416 }
1417 }
1418
1419 if (shouldSkipUpdate) {
1420 // Priority is insufficient. Skip this update. If this is the first
1421 // skipped update, the previous update/state is the new base
1422 // update/state.
1423 const clone: Update<S, A> = {
1424 lane: updateLane,
1425 revertLane: update.revertLane,
1426 gesture: update.gesture,
1427 action: update.action,
1428 hasEagerState: update.hasEagerState,
1429 eagerState: update.eagerState,
1430 next: null as any,
1431 };
1432 if (newBaseQueueLast === null) {
1433 newBaseQueueFirst = newBaseQueueLast = clone;
1434 newBaseState = newState;
1435 } else {
1436 newBaseQueueLast = newBaseQueueLast.next = clone;
1437 }
1438 // Update the remaining priority in the queue.
1439 // TODO: Don't need to accumulate this. Instead, we can remove
1440 // renderLanes from the original lanes.
1441 currentlyRenderingFiber.lanes = mergeLanes(
1442 currentlyRenderingFiber.lanes,
1443 updateLane,
1444 );
1445 markSkippedUpdateLanes(updateLane);
1446 } else {
1447 // This update does have sufficient priority.
1448
1449 // Check if this is an optimistic update.
1450 const revertLane = update.revertLane;
1451 if (revertLane === NoLane) {
1452 // This is not an optimistic update, and we're going to apply it now.
1453 // But, if there were earlier updates that were skipped, we need to
1454 // leave this update in the queue so it can be rebased later.
1455 if (newBaseQueueLast !== null) {
1456 const clone: Update<S, A> = {
1457 // This update is going to be committed so we never want uncommit
1458 // it. Using NoLane works because 0 is a subset of all bitmasks, so
1459 // this will never be skipped by the check above.
1460 lane: NoLane,
1461 revertLane: NoLane,
1462 gesture: null,
1463 action: update.action,
1464 hasEagerState: update.hasEagerState,
1465 eagerState: update.eagerState,
1466 next: null as any,
1467 };
1468 newBaseQueueLast = newBaseQueueLast.next = clone;
1469 }
1470
1471 // Check if this update is part of a pending async action. If so,
1472 // we'll need to suspend until the action has finished, so that it's
1473 // batched together with future updates in the same action.
1474 if (updateLane === peekEntangledActionLane()) {
1475 didReadFromEntangledAsyncAction = true;
1476 }
1477 } else {
1478 // This is an optimistic update. If the "revert" priority is
1479 // sufficient, don't apply the update. Otherwise, apply the update,
1480 // but leave it in the queue so it can be either reverted or
1481 // rebased in a subsequent render.
1482 if (isSubsetOfLanes(renderLanes, revertLane)) {
1483 // The transition that this optimistic update is associated with
1484 // has finished. Pretend the update doesn't exist by skipping
1485 // over it.
1486 update = update.next;
1487
1488 // Check if this update is part of a pending async action. If so,
1489 // we'll need to suspend until the action has finished, so that it's
1490 // batched together with future updates in the same action.
1491 if (revertLane === peekEntangledActionLane()) {
1492 didReadFromEntangledAsyncAction = true;
1493 }
1494 continue;
1495 } else {
1496 const clone: Update<S, A> = {
1497 // Once we commit an optimistic update, we shouldn't uncommit it
1498 // until the transition it is associated with has finished
1499 // (represented by revertLane). Using NoLane here works because 0
1500 // is a subset of all bitmasks, so this will never be skipped by
1501 // the check above.
1502 lane: NoLane,
1503 // Reuse the same revertLane so we know when the transition
1504 // has finished.
1505 revertLane: update.revertLane,
1506 gesture: null, // If it commits, it's no longer a gesture update.
1507 action: update.action,
1508 hasEagerState: update.hasEagerState,
1509 eagerState: update.eagerState,
1510 next: null as any,
1511 };
1512 if (newBaseQueueLast === null) {
1513 newBaseQueueFirst = newBaseQueueLast = clone;
1514 newBaseState = newState;
1515 } else {
1516 newBaseQueueLast = newBaseQueueLast.next = clone;
1517 }
1518 // Update the remaining priority in the queue.
1519 // TODO: Don't need to accumulate this. Instead, we can remove
1520 // renderLanes from the original lanes.
1521 currentlyRenderingFiber.lanes = mergeLanes(
1522 currentlyRenderingFiber.lanes,
1523 revertLane,
1524 );
1525 markSkippedUpdateLanes(revertLane);
1526 }
1527 }
1528
1529 // Process this update.
1530 const action = update.action;
1531 if (shouldDoubleInvokeUserFnsInHooksDEV) {
1532 reducer(newState, action);
1533 }
1534 if (update.hasEagerState) {
1535 // If this update is a state update (not a reducer) and was processed eagerly,
1536 // we can use the eagerly computed state
1537 newState = update.eagerState as any as S;
1538 } else {
1539 newState = reducer(newState, action);
1540 }
1541 }
1542 update = update.next;
1543 // $FlowFixMe[invalid-compare]
1544 } while (update !== null && update !== first);
1545
1546 if (newBaseQueueLast === null) {
1547 newBaseState = newState;
1548 } else {
1549 newBaseQueueLast.next = newBaseQueueFirst as any;
1550 }
1551
1552 // Mark that the fiber performed work, but only if the new state is
1553 // different from the current state.
1554 if (!is(newState, hook.memoizedState)) {
1555 markWorkInProgressReceivedUpdate();
1556
1557 // Check if this update is part of a pending async action. If so, we'll
1558 // need to suspend until the action has finished, so that it's batched
1559 // together with future updates in the same action.
1560 // TODO: Once we support hooks inside useMemo (or an equivalent
1561 // memoization boundary like Forget), hoist this logic so that it only
1562 // suspends if the memo boundary produces a new value.
1563 if (didReadFromEntangledAsyncAction) {
1564 const entangledActionThenable = peekEntangledActionThenable();
1565 if (entangledActionThenable !== null) {
1566 // TODO: Instead of the throwing the thenable directly, throw a
1567 // special object like `use` does so we can detect if it's captured
1568 // by userspace.
1569 throw entangledActionThenable;
1570 }
1571 }
1572 }
1573
1574 hook.memoizedState = newState;
1575 hook.baseState = newBaseState;
1576 hook.baseQueue = newBaseQueueLast;
1577
1578 queue.lastRenderedState = newState;
1579 }
1580
1581 if (baseQueue === null) {
1582 // `queue.lanes` is used for entangling transitions. We can set it back to
1583 // zero once the queue is empty.
1584 queue.lanes = NoLanes;
1585 }
1586
1587 const dispatch: Dispatch<A> = queue.dispatch as any;
1588 return [hook.memoizedState, dispatch];
1589 }
1590
1591 function rerenderReducer<S, I, A>(
1592 reducer: (S, A) => S,
1593 initialArg: I,
1594 init?: I => S,
1595 ): [S, Dispatch<A>] {
1596 const hook = updateWorkInProgressHook();
1597 const queue = hook.queue;
1598
1599 if (queue === null) {
1600 throw new Error(
1601 'Should have a queue. You are likely calling Hooks conditionally, ' +
1602 'which is not allowed. (https://react.dev/link/invalid-hook-call)',
1603 );
1604 }
1605
1606 queue.lastRenderedReducer = reducer;
1607
1608 // This is a re-render. Apply the new render phase updates to the previous
1609 // work-in-progress hook.
1610 const dispatch: Dispatch<A> = queue.dispatch as any;
1611 const lastRenderPhaseUpdate = queue.pending;
1612 let newState = hook.memoizedState;
1613 if (lastRenderPhaseUpdate !== null) {
1614 // The queue doesn't persist past this render pass.
1615 queue.pending = null;
1616
1617 const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
1618 let update = firstRenderPhaseUpdate;
1619 do {
1620 // Process this render phase update. We don't have to check the
1621 // priority because it will always be the same as the current
1622 // render's.
1623 const action = update.action;
1624 newState = reducer(newState, action);
1625 update = update.next;
1626 } while (update !== firstRenderPhaseUpdate);
1627
1628 // Mark that the fiber performed work, but only if the new state is
1629 // different from the current state.
1630 if (!is(newState, hook.memoizedState)) {
1631 markWorkInProgressReceivedUpdate();
1632 }
1633
1634 hook.memoizedState = newState;
1635 // Don't persist the state accumulated from the render phase updates to
1636 // the base state unless the queue is empty.
1637 // TODO: Not sure if this is the desired semantics, but it's what we
1638 // do for gDSFP. I can't remember why.
1639 if (hook.baseQueue === null) {
1640 hook.baseState = newState;
1641 }
1642
1643 queue.lastRenderedState = newState;
1644 }
1645 return [newState, dispatch];
1646 }
1647
1648 function mountSyncExternalStore<T>(
1649 subscribe: (() => void) => () => void,
1650 getSnapshot: () => T,
1651 getServerSnapshot?: () => T,
1652 ): T {
1653 const fiber = currentlyRenderingFiber;
1654 const hook = mountWorkInProgressHook();
1655
1656 let nextSnapshot;
1657 const isHydrating = getIsHydrating();
1658 if (isHydrating) {
1659 if (getServerSnapshot === undefined) {
1660 throw new Error(
1661 'Missing getServerSnapshot, which is required for ' +
1662 'server-rendered content. Will revert to client rendering.',
1663 );
1664 }
1665 nextSnapshot = getServerSnapshot();
1666 if (__DEV__) {
1667 if (!didWarnUncachedGetSnapshot) {
1668 if (nextSnapshot !== getServerSnapshot()) {
1669 console.error(
1670 'The result of getServerSnapshot should be cached to avoid an infinite loop',
1671 );
1672 didWarnUncachedGetSnapshot = true;
1673 }
1674 }
1675 }
1676 } else {
1677 nextSnapshot = getSnapshot();
1678 if (__DEV__) {
1679 if (!didWarnUncachedGetSnapshot) {
1680 const cachedSnapshot = getSnapshot();
1681 if (!is(nextSnapshot, cachedSnapshot)) {
1682 console.error(
1683 'The result of getSnapshot should be cached to avoid an infinite loop',
1684 );
1685 didWarnUncachedGetSnapshot = true;
1686 }
1687 }
1688 }
1689 // Unless we're rendering a blocking lane, schedule a consistency check.
1690 // Right before committing, we will walk the tree and check if any of the
1691 // stores were mutated.
1692 //
1693 // We won't do this if we're hydrating server-rendered content, because if
1694 // the content is stale, it's already visible anyway. Instead we'll patch
1695 // it up in a passive effect.
1696 const root: FiberRoot | null = getWorkInProgressRoot();
1697
1698 if (root === null) {
1699 throw new Error(
1700 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1701 );
1702 }
1703
1704 const rootRenderLanes = getWorkInProgressRootRenderLanes();
1705 if (!includesBlockingLane(rootRenderLanes)) {
1706 pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
1707 }
1708 }
1709
1710 // Read the current snapshot from the store on every render. This breaks the
1711 // normal rules of React, and only works because store updates are
1712 // always synchronous.
1713 hook.memoizedState = nextSnapshot;
1714 const inst: StoreInstance<T> = {
1715 value: nextSnapshot,
1716 getSnapshot,
1717 };
1718 hook.queue = inst;
1719
1720 // Schedule an effect to subscribe to the store.
1721 mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
1722
1723 // Schedule an effect to update the mutable instance fields. We will update
1724 // this whenever subscribe, getSnapshot, or value changes. Because there's no
1725 // clean-up function, and we track the deps correctly, we can call pushEffect
1726 // directly, without storing any additional state. For the same reason, we
1727 // don't need to set a static flag, either.
1728 fiber.flags |= PassiveEffect;
1729 pushSimpleEffect(
1730 HookHasEffect | HookPassive,
1731 createEffectInstance(),
1732 updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
1733 null,
1734 );
1735
1736 return nextSnapshot;
1737 }
1738
1739 function updateSyncExternalStore<T>(
1740 subscribe: (() => void) => () => void,
1741 getSnapshot: () => T,
1742 getServerSnapshot?: () => T,
1743 ): T {
1744 const fiber = currentlyRenderingFiber;
1745 const hook = updateWorkInProgressHook();
1746 // Read the current snapshot from the store on every render. This breaks the
1747 // normal rules of React, and only works because store updates are
1748 // always synchronous.
1749 let nextSnapshot;
1750 const isHydrating = getIsHydrating();
1751 if (isHydrating) {
1752 // Needed for strict mode double render
1753 if (getServerSnapshot === undefined) {
1754 throw new Error(
1755 'Missing getServerSnapshot, which is required for ' +
1756 'server-rendered content. Will revert to client rendering.',
1757 );
1758 }
1759 nextSnapshot = getServerSnapshot();
1760 } else {
1761 nextSnapshot = getSnapshot();
1762 if (__DEV__) {
1763 if (!didWarnUncachedGetSnapshot) {
1764 const cachedSnapshot = getSnapshot();
1765 if (!is(nextSnapshot, cachedSnapshot)) {
1766 console.error(
1767 'The result of getSnapshot should be cached to avoid an infinite loop',
1768 );
1769 didWarnUncachedGetSnapshot = true;
1770 }
1771 }
1772 }
1773 }
1774 const prevSnapshot = (currentHook || hook).memoizedState;
1775 const snapshotChanged = !is(prevSnapshot, nextSnapshot);
1776 if (snapshotChanged) {
1777 hook.memoizedState = nextSnapshot;
1778 markWorkInProgressReceivedUpdate();
1779 }
1780 const inst = hook.queue;
1781
1782 updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [
1783 subscribe,
1784 ]);
1785
1786 // Whenever getSnapshot or subscribe changes, we need to check in the
1787 // commit phase if there was an interleaved mutation. In concurrent mode
1788 // this can happen all the time, but even in synchronous mode, an earlier
1789 // effect may have mutated the store.
1790 const storeChanged =
1791 inst.getSnapshot !== getSnapshot ||
1792 snapshotChanged ||
1793 // Check if the subscribe function changed. We can save some memory by
1794 // checking whether we scheduled a subscription effect above.
1795 (workInProgressHook !== null &&
1796 (workInProgressHook.memoizedState.tag & HookHasEffect) !== HookNoFlags);
1797
1798 // Even if nothing changed during this render, we push the effect so it is
1799 // always in the effect list. That way it re-runs whenever the passive
1800 // effects are reconnected, like when a hidden Activity tree is shown again.
1801 // While the tree was hidden we were not subscribed to the store, so
1802 // mutations during that window notified nobody, and if the reveal didn't
1803 // re-render this component (or rendered before the mutation), nothing
1804 // would ever detect them. When nothing changed, the effect is pushed
1805 // without the HasEffect tag so a regular commit skips it.
1806 pushSimpleEffect(
1807 storeChanged ? HookHasEffect | HookPassive : HookPassive,
1808 createEffectInstance(),
1809 updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
1810 null,
1811 );
1812
1813 if (storeChanged) {
1814 fiber.flags |= PassiveEffect;
1815
1816 // Unless we're rendering a blocking lane, schedule a consistency check.
1817 // Right before committing, we will walk the tree and check if any of the
1818 // stores were mutated.
1819 const root: FiberRoot | null = getWorkInProgressRoot();
1820
1821 if (root === null) {
1822 throw new Error(
1823 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1824 );
1825 }
1826
1827 if (!isHydrating && !includesBlockingLane(renderLanes)) {
1828 pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
1829 }
1830 }
1831
1832 return nextSnapshot;
1833 }
1834
1835 function pushStoreConsistencyCheck<T>(
1836 fiber: Fiber,
1837 getSnapshot: () => T,
1838 renderedSnapshot: T,
1839 ): void {
1840 fiber.flags |= StoreConsistency;
1841 const check: StoreConsistencyCheck<T> = {
1842 getSnapshot,
1843 value: renderedSnapshot,
1844 };
1845 let componentUpdateQueue: null | FunctionComponentUpdateQueue =
1846 currentlyRenderingFiber.updateQueue as any;
1847 if (componentUpdateQueue === null) {
1848 componentUpdateQueue = createFunctionComponentUpdateQueue();
1849 currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;
1850 componentUpdateQueue.stores = [check];
1851 } else {
1852 const stores = componentUpdateQueue.stores;
1853 if (stores === null) {
1854 componentUpdateQueue.stores = [check];
1855 } else {
1856 stores.push(check);
1857 }
1858 }
1859 }
1860
1861 function updateStoreInstance<T>(
1862 fiber: Fiber,
1863 inst: StoreInstance<T>,
1864 nextSnapshot: T,
1865 getSnapshot: () => T,
1866 ): void {
1867 // These are updated in the passive phase
1868 inst.value = nextSnapshot;
1869 inst.getSnapshot = getSnapshot;
1870
1871 // Something may have been mutated in between render and commit. This could
1872 // have been in an event that fired before the passive effects, or it could
1873 // have been in a layout effect. In that case, we would have used the old
1874 // snapshot and getSnapshot values to bail out. We need to check one more
1875 // time. This effect also re-runs when a hidden Activity tree is revealed.
1876 if (checkIfSnapshotChanged(inst)) {
1877 // Force a re-render.
1878 // We intentionally don't log update times and stacks here because this
1879 // was not an external trigger but rather an internal one.
1880 forceStoreRerender(fiber);
1881 }
1882 }
1883
1884 function subscribeToStore<T>(
1885 fiber: Fiber,
1886 inst: StoreInstance<T>,
1887 subscribe: (() => void) => () => void,
1888 ): any {
1889 const handleStoreChange = () => {
1890 // The store changed. Check if the snapshot changed since the last time we
1891 // read from the store.
1892 if (checkIfSnapshotChanged(inst)) {
1893 // Force a re-render.
1894 startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber);
1895 forceStoreRerender(fiber);
1896 }
1897 };
1898 // Subscribe to the store and return a clean-up function.
1899 return subscribe(handleStoreChange);
1900 }
1901
1902 function checkIfSnapshotChanged<T>(inst: StoreInstance<T>): boolean {
1903 const latestGetSnapshot = inst.getSnapshot;
1904 const prevValue = inst.value;
1905 try {
1906 const nextValue = latestGetSnapshot();
1907 return !is(prevValue, nextValue);
1908 } catch (error) {
1909 return true;
1910 }
1911 }
1912
1913 function forceStoreRerender(fiber: Fiber) {
1914 const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
1915 if (root !== null) {
1916 scheduleUpdateOnFiber(root, fiber, SyncLane);
1917 }
1918 }
1919
1920 function mountStateImpl<S>(initialState: (() => S) | S): Hook {
1921 const hook = mountWorkInProgressHook();
1922 if (typeof initialState === 'function') {
1923 const initialStateInitializer = initialState;
1924 // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1925 initialState = initialStateInitializer();
1926 if (shouldDoubleInvokeUserFnsInHooksDEV) {
1927 setIsStrictModeForDevtools(true);
1928 try {
1929 // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
1930 initialStateInitializer();
1931 } finally {
1932 setIsStrictModeForDevtools(false);
1933 }
1934 }
1935 }
1936 hook.memoizedState = hook.baseState = initialState;
1937 const queue: UpdateQueue<S, BasicStateAction<S>> = {
1938 pending: null,
1939 lanes: NoLanes,
1940 dispatch: null,
1941 lastRenderedReducer: basicStateReducer,
1942 lastRenderedState: initialState as any,
1943 };
1944 hook.queue = queue;
1945 return hook;
1946 }
1947
1948 function mountState<S>(
1949 initialState: (() => S) | S,
1950 ): [S, Dispatch<BasicStateAction<S>>] {
1951 const hook = mountStateImpl(initialState);
1952 const queue = hook.queue;
1953 const dispatch: Dispatch<BasicStateAction<S>> = dispatchSetState.bind(
1954 null,
1955 currentlyRenderingFiber,
1956 queue,
1957 ) as any;
1958 queue.dispatch = dispatch;
1959 return [hook.memoizedState, dispatch];
1960 }
1961
1962 function updateState<S>(
1963 initialState: (() => S) | S,
1964 ): [S, Dispatch<BasicStateAction<S>>] {
1965 return updateReducer(basicStateReducer, initialState);
1966 }
1967
1968 function rerenderState<S>(
1969 initialState: (() => S) | S,
1970 ): [S, Dispatch<BasicStateAction<S>>] {
1971 return rerenderReducer(basicStateReducer, initialState);
1972 }
1973
1974 function mountOptimistic<S, A>(
1975 passthrough: S,
1976 reducer: ?(S, A) => S,
1977 ): [S, (A) => void] {
1978 const hook = mountWorkInProgressHook();
1979 hook.memoizedState = hook.baseState = passthrough;
1980 const queue: UpdateQueue<S, A> = {
1981 pending: null,
1982 lanes: NoLanes,
1983 dispatch: null,
1984 // Optimistic state does not use the eager update optimization.
1985 lastRenderedReducer: null,
1986 lastRenderedState: null,
1987 };
1988 hook.queue = queue;
1989 // This is different than the normal setState function.
1990 const dispatch: A => void = dispatchOptimisticSetState.bind(
1991 null,
1992 currentlyRenderingFiber,
1993 true,
1994 queue,
1995 ) as any;
1996 queue.dispatch = dispatch;
1997 return [passthrough, dispatch];
1998 }
1999
2000 function updateOptimistic<S, A>(
2001 passthrough: S,
2002 reducer: ?(S, A) => S,
2003 ): [S, (A) => void] {
2004 const hook = updateWorkInProgressHook();
2005 return updateOptimisticImpl(
2006 hook,
2007 currentHook as any as Hook,
2008 passthrough,
2009 reducer,
2010 );
2011 }
2012
2013 function updateOptimisticImpl<S, A>(
2014 hook: Hook,
2015 current: Hook | null,
2016 passthrough: S,
2017 reducer: ?(S, A) => S,
2018 ): [S, (A) => void] {
2019 // Optimistic updates are always rebased on top of the latest value passed in
2020 // as an argument. It's called a passthrough because if there are no pending
2021 // updates, it will be returned as-is.
2022 //
2023 // Reset the base state to the passthrough. Future updates will be applied
2024 // on top of this.
2025 hook.baseState = passthrough;
2026
2027 // If a reducer is not provided, default to the same one used by useState.
2028 const resolvedReducer: (S, A) => S =
2029 typeof reducer === 'function' ? reducer : (basicStateReducer as any);
2030
2031 return updateReducerImpl(hook, currentHook as any as Hook, resolvedReducer);
2032 }
2033
2034 function rerenderOptimistic<S, A>(
2035 passthrough: S,
2036 reducer: ?(S, A) => S,
2037 ): [S, (A) => void] {
2038 // Unlike useState, useOptimistic doesn't support render phase updates.
2039 // Also unlike useState, we need to replay all pending updates again in case
2040 // the passthrough value changed.
2041 //
2042 // So instead of a forked re-render implementation that knows how to handle
2043 // render phase udpates, we can use the same implementation as during a
2044 // regular mount or update.
2045 const hook = updateWorkInProgressHook();
2046
2047 if (currentHook !== null) {
2048 // This is an update. Process the update queue.
2049 return updateOptimisticImpl(
2050 hook,
2051 currentHook as any as Hook,
2052 passthrough,
2053 reducer,
2054 );
2055 }
2056
2057 // This is a mount. No updates to process.
2058
2059 // Reset the base state to the passthrough. Future updates will be applied
2060 // on top of this.
2061 hook.baseState = passthrough;
2062 const dispatch = hook.queue.dispatch;
2063 return [passthrough, dispatch];
2064 }
2065
2066 // useActionState actions run sequentially, because each action receives the
2067 // previous state as an argument. We store pending actions on a queue.
2068 type ActionStateQueue<S, P> = {
2069 // This is the most recent state returned from an action. It's updated as
2070 // soon as the action finishes running.
2071 state: Awaited<S>,
2072 // A stable dispatch method, passed to the user.
2073 dispatch: Dispatch<P>,
2074 // This is the most recent action function that was rendered. It's updated
2075 // during the commit phase.
2076 // If it's null, it means the action queue errored and subsequent actions
2077 // should not run.
2078 action: ((Awaited<S>, P) => S) | null,
2079 // This is a circular linked list of pending action payloads. It incudes the
2080 // action that is currently running.
2081 pending: ActionStateQueueNode<S, P> | null,
2082 };
2083
2084 type ActionStateQueueNode<S, P> = {
2085 payload: P,
2086 // This is the action implementation at the time it was dispatched.
2087 action: (Awaited<S>, P) => S,
2088 // This is never null because it's part of a circular linked list.
2089 next: ActionStateQueueNode<S, P>,
2090
2091 // Whether or not the action was dispatched as part of a transition. We use
2092 // this to restore the transition context when the queued action is run. Once
2093 // we're able to track parallel async actions, this should be updated to
2094 // represent the specific transition instance the action is associated with.
2095 isTransition: boolean,
2096
2097 // Implements the Thenable interface. We use it to suspend until the action
2098 // finishes.
2099 then: (listener: () => void) => void,
2100 status: 'pending' | 'rejected' | 'fulfilled',
2101 value: any,
2102 reason: any,
2103 listeners: Array<() => void>,
2104 };
2105
2106 function dispatchActionState<S, P>(
2107 fiber: Fiber,
2108 actionQueue: ActionStateQueue<S, P>,
2109 setPendingState: boolean => void,
2110 setState: Dispatch<ActionStateQueueNode<S, P>>,
2111 payload: P,
2112 ): void {
2113 if (isRenderPhaseUpdate(fiber)) {
2114 throw new Error('Cannot update action state while rendering.');
2115 }
2116
2117 const currentAction = actionQueue.action;
2118 if (currentAction === null) {
2119 // An earlier action errored. Subsequent actions should not run.
2120 return;
2121 }
2122
2123 const actionNode: ActionStateQueueNode<S, P> = {
2124 payload,
2125 action: currentAction,
2126 next: null as any, // circular
2127 isTransition: true,
2128
2129 status: 'pending',
2130 value: null,
2131 reason: null,
2132 listeners: [],
2133 then(listener) {
2134 // We know the only thing that subscribes to these promises is `use` so
2135 // this implementation is simpler than a generic thenable. E.g. we don't
2136 // bother to check if the thenable is still pending because `use` already
2137 // does that.
2138 actionNode.listeners.push(listener);
2139 },
2140 };
2141
2142 // Check if we're inside a transition. If so, we'll need to restore the
2143 // transition context when the action is run.
2144 const prevTransition = ReactSharedInternals.T;
2145 if (prevTransition !== null) {
2146 // Optimistically update the pending state, similar to useTransition.
2147 // This will be reverted automatically when all actions are finished.
2148 setPendingState(true);
2149 // `actionNode` is a thenable that resolves to the return value of
2150 // the action.
2151 setState(actionNode);
2152 } else {
2153 // This is not a transition.
2154 actionNode.isTransition = false;
2155 setState(actionNode);
2156 }
2157
2158 const last = actionQueue.pending;
2159 if (last === null) {
2160 // There are no pending actions; this is the first one. We can run
2161 // it immediately.
2162 actionNode.next = actionQueue.pending = actionNode;
2163 runActionStateAction(actionQueue, actionNode);
2164 } else {
2165 // There's already an action running. Add to the queue.
2166 const first = last.next;
2167 actionNode.next = first;
2168 actionQueue.pending = last.next = actionNode;
2169 }
2170 }
2171
2172 function runActionStateAction<S, P>(
2173 actionQueue: ActionStateQueue<S, P>,
2174 node: ActionStateQueueNode<S, P>,
2175 ) {
2176 // `node.action` represents the action function at the time it was dispatched.
2177 // If this action was queued, it might be stale, i.e. it's not necessarily the
2178 // most current implementation of the action, stored on `actionQueue`. This is
2179 // intentional. The conceptual model for queued actions is that they are
2180 // queued in a remote worker; the dispatch happens immediately, only the
2181 // execution is delayed.
2182 const action = node.action;
2183 const payload = node.payload;
2184 const prevState = actionQueue.state;
2185
2186 if (node.isTransition) {
2187 // The original dispatch was part of a transition. We restore its
2188 // transition context here.
2189
2190 // This is a fork of startTransition
2191 const prevTransition = ReactSharedInternals.T;
2192 const currentTransition: Transition = {} as any;
2193 if (enableViewTransition) {
2194 currentTransition.types =
2195 prevTransition !== null
2196 ? // If we're a nested transition, we should use the same set as the parent
2197 // since we're conceptually always joined into the same entangled transition.
2198 // In practice, this only matters if we add transition types in the inner
2199 // without setting state. In that case, the inner transition can finish
2200 // without waiting for the outer.
2201 prevTransition.types
2202 : null;
2203 }
2204 if (enableGestureTransition) {
2205 currentTransition.gesture = null;
2206 }
2207 if (enableTransitionTracing) {
2208 currentTransition.name = null;
2209 currentTransition.startTime = -1;
2210 }
2211 if (__DEV__) {
2212 currentTransition._updatedFibers = new Set();
2213 }
2214 ReactSharedInternals.T = currentTransition;
2215 try {
2216 const returnValue = action(prevState, payload);
2217 const onStartTransitionFinish = ReactSharedInternals.S;
2218 if (onStartTransitionFinish !== null) {
2219 onStartTransitionFinish(currentTransition, returnValue);
2220 }
2221 handleActionReturnValue(actionQueue, node, returnValue);
2222 } catch (error) {
2223 onActionError(actionQueue, node, error);
2224 } finally {
2225 if (prevTransition !== null && currentTransition.types !== null) {
2226 // If we created a new types set in the inner transition, we transfer it to the parent
2227 // since they should share the same set. They're conceptually entangled.
2228 if (__DEV__) {
2229 if (
2230 prevTransition.types !== null &&
2231 prevTransition.types !== currentTransition.types
2232 ) {
2233 // Just assert that assumption holds that we're not overriding anything.
2234 console.error(
2235 'We expected inner Transitions to have transferred the outer types set and ' +
2236 'that you cannot add to the outer Transition while inside the inner.' +
2237 'This is a bug in React.',
2238 );
2239 }
2240 }
2241 prevTransition.types = currentTransition.types;
2242 }
2243 ReactSharedInternals.T = prevTransition;
2244
2245 if (__DEV__) {
2246 if (prevTransition === null && currentTransition._updatedFibers) {
2247 const updatedFibersCount = currentTransition._updatedFibers.size;
2248 currentTransition._updatedFibers.clear();
2249 if (updatedFibersCount > 10) {
2250 console.warn(
2251 'Detected a large number of updates inside startTransition. ' +
2252 'If this is due to a subscription please re-write it to use React provided hooks. ' +
2253 'Otherwise concurrent mode guarantees are off the table.',
2254 );
2255 }
2256 }
2257 }
2258 }
2259 } else {
2260 // The original dispatch was not part of a transition.
2261 try {
2262 const returnValue = action(prevState, payload);
2263 handleActionReturnValue(actionQueue, node, returnValue);
2264 } catch (error) {
2265 onActionError(actionQueue, node, error);
2266 }
2267 }
2268 }
2269
2270 function handleActionReturnValue<S, P>(
2271 actionQueue: ActionStateQueue<S, P>,
2272 node: ActionStateQueueNode<S, P>,
2273 returnValue: mixed,
2274 ) {
2275 if (
2276 returnValue !== null &&
2277 typeof returnValue === 'object' &&
2278 // $FlowFixMe[method-unbinding]
2279 typeof returnValue.then === 'function'
2280 ) {
2281 const thenable = returnValue as any as Thenable<Awaited<S>>;
2282 if (__DEV__) {
2283 // Keep track of the number of async transitions still running so we can warn.
2284 ReactSharedInternals.asyncTransitions++;
2285 thenable.then(releaseAsyncTransition, releaseAsyncTransition);
2286 }
2287 // Attach a listener to read the return state of the action. As soon as
2288 // this resolves, we can run the next action in the sequence.
2289 thenable.then(
2290 (nextState: Awaited<S>) => {
2291 onActionSuccess(actionQueue, node, nextState);
2292 },
2293 (error: mixed) => onActionError(actionQueue, node, error),
2294 );
2295
2296 if (__DEV__) {
2297 if (!node.isTransition) {
2298 console.error(
2299 'An async function with useActionState was called outside of a transition. ' +
2300 'This is likely not what you intended (for example, isPending will not update ' +
2301 'correctly). Either call the returned function inside startTransition, or pass it ' +
2302 'to an `action` or `formAction` prop.',
2303 );
2304 }
2305 }
2306 } else {
2307 const nextState = returnValue as any as Awaited<S>;
2308 onActionSuccess(actionQueue, node, nextState);
2309 }
2310 }
2311
2312 function onActionSuccess<S, P>(
2313 actionQueue: ActionStateQueue<S, P>,
2314 actionNode: ActionStateQueueNode<S, P>,
2315 nextState: Awaited<S>,
2316 ) {
2317 // The action finished running.
2318 actionNode.status = 'fulfilled';
2319 actionNode.value = nextState;
2320 notifyActionListeners(actionNode);
2321
2322 actionQueue.state = nextState;
2323
2324 // Pop the action from the queue and run the next pending action, if there
2325 // are any.
2326 const last = actionQueue.pending;
2327 if (last !== null) {
2328 const first = last.next;
2329 if (first === last) {
2330 // This was the last action in the queue.
2331 actionQueue.pending = null;
2332 } else {
2333 // Remove the first node from the circular queue.
2334 const next = first.next;
2335 last.next = next;
2336
2337 // Run the next action.
2338 runActionStateAction(actionQueue, next);
2339 }
2340 }
2341 }
2342
2343 function onActionError<S, P>(
2344 actionQueue: ActionStateQueue<S, P>,
2345 actionNode: ActionStateQueueNode<S, P>,
2346 error: mixed,
2347 ) {
2348 // Mark all the following actions as rejected.
2349 const last = actionQueue.pending;
2350 actionQueue.pending = null;
2351 if (last !== null) {
2352 const first = last.next;
2353 do {
2354 actionNode.status = 'rejected';
2355 actionNode.reason = error;
2356 notifyActionListeners(actionNode);
2357 actionNode = actionNode.next;
2358 } while (actionNode !== first);
2359 }
2360
2361 // Prevent subsequent actions from being dispatched.
2362 actionQueue.action = null;
2363 }
2364
2365 function notifyActionListeners<S, P>(actionNode: ActionStateQueueNode<S, P>) {
2366 // Notify React that the action has finished.
2367 const listeners = actionNode.listeners;
2368 for (let i = 0; i < listeners.length; i++) {
2369 // This is always a React internal listener, so we don't need to worry
2370 // about it throwing.
2371 const listener = listeners[i];
2372 listener();
2373 }
2374 }
2375
2376 function actionStateReducer<S>(oldState: S, newState: S): S {
2377 return newState;
2378 }
2379
2380 function mountActionState<S, P>(
2381 action: (Awaited<S>, P) => S,
2382 initialStateProp: Awaited<S>,
2383 permalink?: string,
2384 ): [Awaited<S>, (P) => void, boolean] {
2385 let initialState: Awaited<S> = initialStateProp;
2386 if (getIsHydrating()) {
2387 const root: FiberRoot = getWorkInProgressRoot() as any;
2388 const ssrFormState = root.formState;
2389 // If a formState option was passed to the root, there are form state
2390 // markers that we need to hydrate. These indicate whether the form state
2391 // matches this hook instance.
2392 if (ssrFormState !== null) {
2393 const isMatching = tryToClaimNextHydratableFormMarkerInstance(
2394 currentlyRenderingFiber,
2395 );
2396 if (isMatching) {
2397 initialState = ssrFormState[0];
2398 }
2399 }
2400 }
2401
2402 // State hook. The state is stored in a thenable which is then unwrapped by
2403 // the `use` algorithm during render.
2404 const stateHook = mountWorkInProgressHook();
2405 stateHook.memoizedState = stateHook.baseState = initialState;
2406 // TODO: Typing this "correctly" results in recursion limit errors
2407 // const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {
2408 const stateQueue = {
2409 pending: null,
2410 lanes: NoLanes,
2411 dispatch: null as any,
2412 lastRenderedReducer: actionStateReducer,
2413 lastRenderedState: initialState,
2414 };
2415 stateHook.queue = stateQueue;
2416 const setState: Dispatch<S | Awaited<S>> = dispatchSetState.bind(
2417 null,
2418 currentlyRenderingFiber,
2419 stateQueue as any as UpdateQueue<S | Awaited<S>, S | Awaited<S>>,
2420 ) as any;
2421 stateQueue.dispatch = setState;
2422
2423 // Pending state. This is used to store the pending state of the action.
2424 // Tracked optimistically, like a transition pending state.
2425 const pendingStateHook = mountStateImpl(false as Thenable<boolean> | boolean);
2426 const setPendingState: boolean => void = dispatchOptimisticSetState.bind(
2427 null,
2428 currentlyRenderingFiber,
2429 false,
2430 pendingStateHook.queue as any as UpdateQueue<
2431 S | Awaited<S>,
2432 S | Awaited<S>,
2433 >,
2434 ) as any;
2435
2436 // Action queue hook. This is used to queue pending actions. The queue is
2437 // shared between all instances of the hook. Similar to a regular state queue,
2438 // but different because the actions are run sequentially, and they run in
2439 // an event instead of during render.
2440 const actionQueueHook = mountWorkInProgressHook();
2441 const actionQueue: ActionStateQueue<S, P> = {
2442 state: initialState,
2443 dispatch: null as any, // circular
2444 action,
2445 pending: null,
2446 };
2447 actionQueueHook.queue = actionQueue;
2448 const dispatch = (dispatchActionState as any).bind(
2449 null,
2450 currentlyRenderingFiber,
2451 actionQueue,
2452 setPendingState,
2453 setState,
2454 );
2455 actionQueue.dispatch = dispatch;
2456
2457 // Stash the action function on the memoized state of the hook. We'll use this
2458 // to detect when the action function changes so we can update it in
2459 // an effect.
2460 actionQueueHook.memoizedState = action;
2461
2462 return [initialState, dispatch, false];
2463 }
2464
2465 function updateActionState<S, P>(
2466 action: (Awaited<S>, P) => S,
2467 initialState: Awaited<S>,
2468 permalink?: string,
2469 ): [Awaited<S>, (P) => void, boolean] {
2470 const stateHook = updateWorkInProgressHook();
2471 const currentStateHook = currentHook as any as Hook;
2472 return updateActionStateImpl(
2473 stateHook,
2474 currentStateHook,
2475 action,
2476 initialState,
2477 permalink,
2478 );
2479 }
2480
2481 function updateActionStateImpl<S, P>(
2482 stateHook: Hook,
2483 currentStateHook: Hook,
2484 action: (Awaited<S>, P) => S,
2485 initialState: Awaited<S>,
2486 permalink?: string,
2487 ): [Awaited<S>, (P) => void, boolean] {
2488 const [actionResult] = updateReducerImpl<S | Thenable<S>, S | Thenable<S>>(
2489 stateHook,
2490 currentStateHook,
2491 actionStateReducer,
2492 );
2493
2494 const [isPending] = updateState(false);
2495
2496 // This will suspend until the action finishes.
2497 let state: Awaited<S>;
2498 if (
2499 typeof actionResult === 'object' &&
2500 // $FlowFixMe[invalid-compare]
2501 actionResult !== null &&
2502 // $FlowFixMe[method-unbinding]
2503 typeof actionResult.then === 'function'
2504 ) {
2505 try {
2506 state = useThenable(actionResult as any as Thenable<Awaited<S>>);
2507 } catch (x) {
2508 if (x === SuspenseException) {
2509 // If we Suspend here, mark this separately so that we can track this
2510 // as an Action in Profiling tools.
2511 throw SuspenseActionException;
2512 } else {
2513 throw x;
2514 }
2515 }
2516 } else {
2517 state = actionResult as any;
2518 }
2519
2520 const actionQueueHook = updateWorkInProgressHook();
2521 const actionQueue = actionQueueHook.queue;
2522 const dispatch = actionQueue.dispatch;
2523
2524 // Check if a new action was passed. If so, update it in an effect.
2525 const prevAction = actionQueueHook.memoizedState;
2526 if (action !== prevAction) {
2527 currentlyRenderingFiber.flags |= PassiveEffect;
2528 pushSimpleEffect(
2529 HookHasEffect | HookPassive,
2530 createEffectInstance(),
2531 actionStateActionEffect.bind(null, actionQueue, action),
2532 null,
2533 );
2534 }
2535
2536 return [state, dispatch, isPending];
2537 }
2538
2539 function actionStateActionEffect<S, P>(
2540 actionQueue: ActionStateQueue<S, P>,
2541 action: (Awaited<S>, P) => S,
2542 ): void {
2543 actionQueue.action = action;
2544 }
2545
2546 function rerenderActionState<S, P>(
2547 action: (Awaited<S>, P) => S,
2548 initialState: Awaited<S>,
2549 permalink?: string,
2550 ): [Awaited<S>, (P) => void, boolean] {
2551 // Unlike useState, useActionState doesn't support render phase updates.
2552 // Also unlike useState, we need to replay all pending updates again in case
2553 // the passthrough value changed.
2554 //
2555 // So instead of a forked re-render implementation that knows how to handle
2556 // render phase udpates, we can use the same implementation as during a
2557 // regular mount or update.
2558 const stateHook = updateWorkInProgressHook();
2559 const currentStateHook = currentHook;
2560
2561 if (currentStateHook !== null) {
2562 // This is an update. Process the update queue.
2563 return updateActionStateImpl(
2564 stateHook,
2565 currentStateHook,
2566 action,
2567 initialState,
2568 permalink,
2569 );
2570 }
2571
2572 updateWorkInProgressHook(); // State
2573
2574 // This is a mount. No updates to process.
2575 const state: Awaited<S> = stateHook.memoizedState;
2576
2577 const actionQueueHook = updateWorkInProgressHook();
2578 const actionQueue = actionQueueHook.queue;
2579 const dispatch = actionQueue.dispatch;
2580
2581 // This may have changed during the rerender.
2582 actionQueueHook.memoizedState = action;
2583
2584 // For mount, pending is always false.
2585 return [state, dispatch, false];
2586 }
2587
2588 function pushSimpleEffect(
2589 tag: HookFlags,
2590 inst: EffectInstance,
2591 create: () => (() => void) | void,
2592 deps: Array<mixed> | void | null,
2593 ): Effect {
2594 const effect: Effect = {
2595 tag,
2596 create,
2597 deps,
2598 inst,
2599 // Circular
2600 next: null as any,
2601 };
2602 return pushEffectImpl(effect);
2603 }
2604
2605 function pushEffectImpl(effect: Effect): Effect {
2606 let componentUpdateQueue: null | FunctionComponentUpdateQueue =
2607 currentlyRenderingFiber.updateQueue as any;
2608 if (componentUpdateQueue === null) {
2609 componentUpdateQueue = createFunctionComponentUpdateQueue();
2610 currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;
2611 }
2612 const lastEffect = componentUpdateQueue.lastEffect;
2613 if (lastEffect === null) {
2614 componentUpdateQueue.lastEffect = effect.next = effect;
2615 } else {
2616 const firstEffect = lastEffect.next;
2617 lastEffect.next = effect;
2618 effect.next = firstEffect;
2619 componentUpdateQueue.lastEffect = effect;
2620 }
2621 return effect;
2622 }
2623
2624 function createEffectInstance(): EffectInstance {
2625 return {destroy: undefined};
2626 }
2627
2628 function mountRef<T>(initialValue: T): {current: T} {
2629 const hook = mountWorkInProgressHook();
2630 const ref = {current: initialValue};
2631 hook.memoizedState = ref;
2632 return ref;
2633 }
2634
2635 function updateRef<T>(initialValue: T): {current: T} {
2636 const hook = updateWorkInProgressHook();
2637 return hook.memoizedState;
2638 }
2639
2640 function mountEffectImpl(
2641 fiberFlags: Flags,
2642 hookFlags: HookFlags,
2643 create: () => (() => void) | void,
2644 deps: Array<mixed> | void | null,
2645 ): void {
2646 const hook = mountWorkInProgressHook();
2647 const nextDeps = deps === undefined ? null : deps;
2648 currentlyRenderingFiber.flags |= fiberFlags;
2649 hook.memoizedState = pushSimpleEffect(
2650 HookHasEffect | hookFlags,
2651 createEffectInstance(),
2652 create,
2653 nextDeps,
2654 );
2655 }
2656
2657 function updateEffectImpl(
2658 fiberFlags: Flags,
2659 hookFlags: HookFlags,
2660 create: () => (() => void) | void,
2661 deps: Array<mixed> | void | null,
2662 ): void {
2663 const hook = updateWorkInProgressHook();
2664 const nextDeps = deps === undefined ? null : deps;
2665 const effect: Effect = hook.memoizedState;
2666 const inst = effect.inst;
2667
2668 // currentHook is null on initial mount when rerendering after a render phase
2669 // state update or for strict mode.
2670 if (currentHook !== null) {
2671 if (nextDeps !== null) {
2672 const prevEffect: Effect = currentHook.memoizedState;
2673 const prevDeps = prevEffect.deps;
2674 // $FlowFixMe[incompatible-type] (@poteto)
2675 if (areHookInputsEqual(nextDeps, prevDeps)) {
2676 hook.memoizedState = pushSimpleEffect(
2677 hookFlags,
2678 inst,
2679 create,
2680 nextDeps,
2681 );
2682 return;
2683 }
2684 }
2685 }
2686
2687 currentlyRenderingFiber.flags |= fiberFlags;
2688
2689 hook.memoizedState = pushSimpleEffect(
2690 HookHasEffect | hookFlags,
2691 inst,
2692 create,
2693 nextDeps,
2694 );
2695 }
2696
2697 function mountEffect(
2698 create: () => (() => void) | void,
2699 deps: Array<mixed> | void | null,
2700 ): void {
2701 if (
2702 __DEV__ &&
2703 (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2704 ) {
2705 mountEffectImpl(
2706 MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
2707 HookPassive,
2708 create,
2709 deps,
2710 );
2711 } else {
2712 mountEffectImpl(
2713 PassiveEffect | PassiveStaticEffect,
2714 HookPassive,
2715 create,
2716 deps,
2717 );
2718 }
2719 }
2720
2721 function updateEffect(
2722 create: () => (() => void) | void,
2723 deps: Array<mixed> | void | null,
2724 ): void {
2725 updateEffectImpl(PassiveEffect, HookPassive, create, deps);
2726 }
2727
2728 function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
2729 payload: EventFunctionPayload<Args, Return, F>,
2730 ) {
2731 currentlyRenderingFiber.flags |= UpdateEffect;
2732 let componentUpdateQueue: null | FunctionComponentUpdateQueue =
2733 currentlyRenderingFiber.updateQueue as any;
2734 if (componentUpdateQueue === null) {
2735 componentUpdateQueue = createFunctionComponentUpdateQueue();
2736 currentlyRenderingFiber.updateQueue = componentUpdateQueue as any;
2737 componentUpdateQueue.events = [payload];
2738 } else {
2739 const events = componentUpdateQueue.events;
2740 if (events === null) {
2741 componentUpdateQueue.events = [payload];
2742 } else {
2743 events.push(payload);
2744 }
2745 }
2746 }
2747
2748 function mountEvent<Args, Return, F: (...Array<Args>) => Return>(
2749 callback: F,
2750 ): F {
2751 const hook = mountWorkInProgressHook();
2752 const ref = {impl: callback};
2753 hook.memoizedState = ref;
2754 // $FlowFixMe[incompatible-type]
2755 return function eventFn() {
2756 if (isInvalidExecutionContextForEventFunction()) {
2757 throw new Error(
2758 "A function wrapped in useEffectEvent can't be called during rendering.",
2759 );
2760 }
2761 return ref.impl.apply(undefined, arguments);
2762 };
2763 }
2764
2765 function updateEvent<Args, Return, F: (...Array<Args>) => Return>(
2766 callback: F,
2767 ): F {
2768 const hook = updateWorkInProgressHook();
2769 const ref = hook.memoizedState;
2770 useEffectEventImpl({ref, nextImpl: callback});
2771 // $FlowFixMe[incompatible-type]
2772 return function eventFn() {
2773 if (isInvalidExecutionContextForEventFunction()) {
2774 throw new Error(
2775 "A function wrapped in useEffectEvent can't be called during rendering.",
2776 );
2777 }
2778 return ref.impl.apply(undefined, arguments);
2779 };
2780 }
2781
2782 function mountInsertionEffect(
2783 create: () => (() => void) | void,
2784 deps: Array<mixed> | void | null,
2785 ): void {
2786 mountEffectImpl(UpdateEffect, HookInsertion, create, deps);
2787 }
2788
2789 function updateInsertionEffect(
2790 create: () => (() => void) | void,
2791 deps: Array<mixed> | void | null,
2792 ): void {
2793 return updateEffectImpl(UpdateEffect, HookInsertion, create, deps);
2794 }
2795
2796 function mountLayoutEffect(
2797 create: () => (() => void) | void,
2798 deps: Array<mixed> | void | null,
2799 ): void {
2800 let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2801 if (
2802 __DEV__ &&
2803 (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2804 ) {
2805 fiberFlags |= MountLayoutDevEffect;
2806 }
2807 return mountEffectImpl(fiberFlags, HookLayout, create, deps);
2808 }
2809
2810 function updateLayoutEffect(
2811 create: () => (() => void) | void,
2812 deps: Array<mixed> | void | null,
2813 ): void {
2814 return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
2815 }
2816
2817 function imperativeHandleEffect<T>(
2818 create: () => T,
2819 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
2820 ): void | (() => void) {
2821 if (typeof ref === 'function') {
2822 const refCallback = ref;
2823 const inst = create();
2824 const refCleanup = refCallback(inst);
2825 return () => {
2826 if (typeof refCleanup === 'function') {
2827 // $FlowFixMe[incompatible-use] we need to assume no parameters
2828 refCleanup();
2829 } else {
2830 refCallback(null);
2831 }
2832 };
2833 } else if (ref !== null && ref !== undefined) {
2834 const refObject = ref;
2835 if (__DEV__) {
2836 if (!refObject.hasOwnProperty('current')) {
2837 console.error(
2838 'Expected useImperativeHandle() first argument to either be a ' +
2839 'ref callback or React.createRef() object. Instead received: %s.',
2840 'an object with keys {' + Object.keys(refObject).join(', ') + '}',
2841 );
2842 }
2843 }
2844 const inst = create();
2845 refObject.current = inst;
2846 return () => {
2847 refObject.current = null;
2848 };
2849 }
2850 }
2851
2852 function mountImperativeHandle<T>(
2853 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
2854 create: () => T,
2855 deps: Array<mixed> | void | null,
2856 ): void {
2857 if (__DEV__) {
2858 if (typeof create !== 'function') {
2859 console.error(
2860 'Expected useImperativeHandle() second argument to be a function ' +
2861 'that creates a handle. Instead received: %s.',
2862 // $FlowFixMe[invalid-compare]
2863 create !== null ? typeof create : 'null',
2864 );
2865 }
2866 }
2867
2868 // TODO: If deps are provided, should we skip comparing the ref itself?
2869 const effectDeps =
2870 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
2871
2872 let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;
2873 if (
2874 __DEV__ &&
2875 (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
2876 ) {
2877 fiberFlags |= MountLayoutDevEffect;
2878 }
2879 mountEffectImpl(
2880 fiberFlags,
2881 HookLayout,
2882 imperativeHandleEffect.bind(null, create, ref),
2883 effectDeps,
2884 );
2885 }
2886
2887 function updateImperativeHandle<T>(
2888 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
2889 create: () => T,
2890 deps: Array<mixed> | void | null,
2891 ): void {
2892 if (__DEV__) {
2893 if (typeof create !== 'function') {
2894 console.error(
2895 'Expected useImperativeHandle() second argument to be a function ' +
2896 'that creates a handle. Instead received: %s.',
2897 // $FlowFixMe[invalid-compare]
2898 create !== null ? typeof create : 'null',
2899 );
2900 }
2901 }
2902
2903 // TODO: If deps are provided, should we skip comparing the ref itself?
2904 const effectDeps =
2905 deps !== null && deps !== undefined ? deps.concat([ref]) : null;
2906
2907 updateEffectImpl(
2908 UpdateEffect,
2909 HookLayout,
2910 imperativeHandleEffect.bind(null, create, ref),
2911 effectDeps,
2912 );
2913 }
2914
2915 function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2916 // This hook is normally a no-op.
2917 // The react-debug-hooks package injects its own implementation
2918 // so that e.g. DevTools can display custom hook values.
2919 }
2920
2921 const updateDebugValue = mountDebugValue;
2922
2923 function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2924 const hook = mountWorkInProgressHook();
2925 const nextDeps = deps === undefined ? null : deps;
2926 hook.memoizedState = [callback, nextDeps];
2927 return callback;
2928 }
2929
2930 function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2931 const hook = updateWorkInProgressHook();
2932 const nextDeps = deps === undefined ? null : deps;
2933 const prevState = hook.memoizedState;
2934 if (nextDeps !== null) {
2935 const prevDeps: Array<mixed> | null = prevState[1];
2936 if (areHookInputsEqual(nextDeps, prevDeps)) {
2937 return prevState[0];
2938 }
2939 }
2940 hook.memoizedState = [callback, nextDeps];
2941 return callback;
2942 }
2943
2944 function mountMemo<T>(
2945 nextCreate: () => T,
2946 deps: Array<mixed> | void | null,
2947 ): T {
2948 const hook = mountWorkInProgressHook();
2949 const nextDeps = deps === undefined ? null : deps;
2950 const nextValue = nextCreate();
2951 if (shouldDoubleInvokeUserFnsInHooksDEV) {
2952 setIsStrictModeForDevtools(true);
2953 try {
2954 nextCreate();
2955 } finally {
2956 setIsStrictModeForDevtools(false);
2957 }
2958 }
2959 hook.memoizedState = [nextValue, nextDeps];
2960 return nextValue;
2961 }
2962
2963 function updateMemo<T>(
2964 nextCreate: () => T,
2965 deps: Array<mixed> | void | null,
2966 ): T {
2967 const hook = updateWorkInProgressHook();
2968 const nextDeps = deps === undefined ? null : deps;
2969 const prevState = hook.memoizedState;
2970 // Assume these are defined. If they're not, areHookInputsEqual will warn.
2971 if (nextDeps !== null) {
2972 const prevDeps: Array<mixed> | null = prevState[1];
2973 if (areHookInputsEqual(nextDeps, prevDeps)) {
2974 return prevState[0];
2975 }
2976 }
2977 const nextValue = nextCreate();
2978 if (shouldDoubleInvokeUserFnsInHooksDEV) {
2979 setIsStrictModeForDevtools(true);
2980 try {
2981 nextCreate();
2982 } finally {
2983 setIsStrictModeForDevtools(false);
2984 }
2985 }
2986 hook.memoizedState = [nextValue, nextDeps];
2987 return nextValue;
2988 }
2989
2990 function mountDeferredValue<T>(value: T, initialValue?: T): T {
2991 const hook = mountWorkInProgressHook();
2992 return mountDeferredValueImpl(hook, value, initialValue);
2993 }
2994
2995 function updateDeferredValue<T>(value: T, initialValue?: T): T {
2996 const hook = updateWorkInProgressHook();
2997 const resolvedCurrentHook: Hook = currentHook as any;
2998 const prevValue: T = resolvedCurrentHook.memoizedState;
2999 return updateDeferredValueImpl(hook, prevValue, value, initialValue);
3000 }
3001
3002 function rerenderDeferredValue<T>(value: T, initialValue?: T): T {
3003 const hook = updateWorkInProgressHook();
3004 if (currentHook === null) {
3005 // This is a rerender during a mount.
3006 return mountDeferredValueImpl(hook, value, initialValue);
3007 } else {
3008 // This is a rerender during an update.
3009 const prevValue: T = currentHook.memoizedState;
3010 return updateDeferredValueImpl(hook, prevValue, value, initialValue);
3011 }
3012 }
3013
3014 function isRenderingDeferredWork(): boolean {
3015 if (!includesSomeLane(renderLanes, DeferredLane)) {
3016 // None of the render lanes are deferred lanes.
3017 return false;
3018 }
3019 // At least one of the render lanes are deferred lanes. However, if the
3020 // current render is also batched together with an update, then we can't
3021 // say that the render is wholly the result of deferred work. We can check
3022 // this by checking if the root render lanes contain any "update" lanes, i.e.
3023 // lanes that are only assigned to updates, like setState.
3024 const rootRenderLanes = getWorkInProgressRootRenderLanes();
3025 return !includesSomeLane(rootRenderLanes, UpdateLanes);
3026 }
3027
3028 function mountDeferredValueImpl<T>(hook: Hook, value: T, initialValue?: T): T {
3029 if (
3030 // When `initialValue` is provided, we defer the initial render even if the
3031 // current render is not synchronous.
3032 initialValue !== undefined &&
3033 // However, to avoid waterfalls, we do not defer if this render
3034 // was itself spawned by an earlier useDeferredValue. Check if DeferredLane
3035 // is part of the render lanes.
3036 !isRenderingDeferredWork()
3037 ) {
3038 // Render with the initial value
3039 hook.memoizedState = initialValue;
3040
3041 // Schedule a deferred render to switch to the final value.
3042 const deferredLane = requestDeferredLane();
3043 currentlyRenderingFiber.lanes = mergeLanes(
3044 currentlyRenderingFiber.lanes,
3045 deferredLane,
3046 );
3047 markSkippedUpdateLanes(deferredLane);
3048
3049 return initialValue;
3050 } else {
3051 hook.memoizedState = value;
3052 return value;
3053 }
3054 }
3055
3056 function updateDeferredValueImpl<T>(
3057 hook: Hook,
3058 prevValue: T,
3059 value: T,
3060 initialValue?: T,
3061 ): T {
3062 if (is(value, prevValue)) {
3063 // The incoming value is referentially identical to the currently rendered
3064 // value, so we can bail out quickly.
3065 return value;
3066 } else {
3067 // Received a new value that's different from the current value.
3068
3069 // Check if we're inside a hidden tree
3070 if (isCurrentTreeHidden()) {
3071 // Revealing a prerendered tree is considered the same as mounting new
3072 // one, so we reuse the "mount" path in this case.
3073 const resultValue = mountDeferredValueImpl(hook, value, initialValue);
3074 // Unlike during an actual mount, we need to mark this as an update if
3075 // the value changed.
3076 if (!is(resultValue, prevValue)) {
3077 markWorkInProgressReceivedUpdate();
3078 }
3079 return resultValue;
3080 }
3081
3082 const shouldDeferValue =
3083 !includesOnlyNonUrgentLanes(renderLanes) && !isRenderingDeferredWork();
3084 if (shouldDeferValue) {
3085 // This is an urgent update. Since the value has changed, keep using the
3086 // previous value and spawn a deferred render to update it later.
3087
3088 // Schedule a deferred render
3089 const deferredLane = requestDeferredLane();
3090 currentlyRenderingFiber.lanes = mergeLanes(
3091 currentlyRenderingFiber.lanes,
3092 deferredLane,
3093 );
3094 markSkippedUpdateLanes(deferredLane);
3095
3096 // Reuse the previous value. We do not need to mark this as an update,
3097 // because we did not render a new value.
3098 return prevValue;
3099 } else {
3100 // This is not an urgent update, so we can use the latest value regardless
3101 // of what it is. No need to defer it.
3102
3103 // Mark this as an update to prevent the fiber from bailing out.
3104 markWorkInProgressReceivedUpdate();
3105 hook.memoizedState = value;
3106 return value;
3107 }
3108 }
3109 }
3110
3111 function releaseAsyncTransition() {
3112 if (__DEV__) {
3113 ReactSharedInternals.asyncTransitions--;
3114 }
3115 }
3116
3117 function startTransition<S>(
3118 fiber: Fiber,
3119 queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,
3120 pendingState: S,
3121 finishedState: S,
3122 callback: () => mixed,
3123 options?: StartTransitionOptions,
3124 ): void {
3125 const previousPriority = getCurrentUpdatePriority();
3126 setCurrentUpdatePriority(
3127 higherEventPriority(previousPriority, ContinuousEventPriority),
3128 );
3129
3130 const prevTransition = ReactSharedInternals.T;
3131 const currentTransition: Transition = {} as any;
3132 if (enableViewTransition) {
3133 currentTransition.types =
3134 prevTransition !== null
3135 ? // If we're a nested transition, we should use the same set as the parent
3136 // since we're conceptually always joined into the same entangled transition.
3137 // In practice, this only matters if we add transition types in the inner
3138 // without setting state. In that case, the inner transition can finish
3139 // without waiting for the outer.
3140 prevTransition.types
3141 : null;
3142 }
3143 if (enableGestureTransition) {
3144 currentTransition.gesture = null;
3145 }
3146 if (enableTransitionTracing) {
3147 currentTransition.name =
3148 options !== undefined && options.name !== undefined ? options.name : null;
3149 currentTransition.startTime = now();
3150 }
3151 if (__DEV__) {
3152 currentTransition._updatedFibers = new Set();
3153 }
3154
3155 // We don't really need to use an optimistic update here, because we
3156 // schedule a second "revert" update below (which we use to suspend the
3157 // transition until the async action scope has finished). But we'll use an
3158 // optimistic update anyway to make it less likely the behavior accidentally
3159 // diverges; for example, both an optimistic update and this one should
3160 // share the same lane.
3161 ReactSharedInternals.T = currentTransition;
3162 dispatchOptimisticSetState(fiber, false, queue, pendingState);
3163
3164 try {
3165 const returnValue = callback();
3166 const onStartTransitionFinish = ReactSharedInternals.S;
3167 if (onStartTransitionFinish !== null) {
3168 onStartTransitionFinish(currentTransition, returnValue);
3169 }
3170
3171 // Check if we're inside an async action scope. If so, we'll entangle
3172 // this new action with the existing scope.
3173 //
3174 // If we're not already inside an async action scope, and this action is
3175 // async, then we'll create a new async scope.
3176 //
3177 // In the async case, the resulting render will suspend until the async
3178 // action scope has finished.
3179 if (
3180 returnValue !== null &&
3181 typeof returnValue === 'object' &&
3182 typeof returnValue.then === 'function'
3183 ) {
3184 const thenable = returnValue as any as Thenable<mixed>;
3185 if (__DEV__) {
3186 // Keep track of the number of async transitions still running so we can warn.
3187 ReactSharedInternals.asyncTransitions++;
3188 thenable.then(releaseAsyncTransition, releaseAsyncTransition);
3189 }
3190 // Create a thenable that resolves to `finishedState` once the async
3191 // action has completed.
3192 const thenableForFinishedState = chainThenableValue(
3193 thenable,
3194 finishedState,
3195 );
3196 dispatchSetStateInternal(
3197 fiber,
3198 queue,
3199 thenableForFinishedState as any,
3200 requestUpdateLane(fiber),
3201 );
3202 } else {
3203 dispatchSetStateInternal(
3204 fiber,
3205 queue,
3206 finishedState,
3207 requestUpdateLane(fiber),
3208 );
3209 }
3210 } catch (error) {
3211 // This is a trick to get the `useTransition` hook to rethrow the error.
3212 // When it unwraps the thenable with the `use` algorithm, the error
3213 // will be thrown.
3214 const rejectedThenable: RejectedThenable<S> = {
3215 then() {},
3216 status: 'rejected',
3217 reason: error,
3218 };
3219 dispatchSetStateInternal(
3220 fiber,
3221 queue,
3222 rejectedThenable,
3223 requestUpdateLane(fiber),
3224 );
3225 } finally {
3226 setCurrentUpdatePriority(previousPriority);
3227
3228 if (prevTransition !== null && currentTransition.types !== null) {
3229 // If we created a new types set in the inner transition, we transfer it to the parent
3230 // since they should share the same set. They're conceptually entangled.
3231 if (__DEV__) {
3232 if (
3233 prevTransition.types !== null &&
3234 prevTransition.types !== currentTransition.types
3235 ) {
3236 // Just assert that assumption holds that we're not overriding anything.
3237 console.error(
3238 'We expected inner Transitions to have transferred the outer types set and ' +
3239 'that you cannot add to the outer Transition while inside the inner.' +
3240 'This is a bug in React.',
3241 );
3242 }
3243 }
3244 prevTransition.types = currentTransition.types;
3245 }
3246 ReactSharedInternals.T = prevTransition;
3247
3248 if (__DEV__) {
3249 if (prevTransition === null && currentTransition._updatedFibers) {
3250 const updatedFibersCount = currentTransition._updatedFibers.size;
3251 currentTransition._updatedFibers.clear();
3252 if (updatedFibersCount > 10) {
3253 console.warn(
3254 'Detected a large number of updates inside startTransition. ' +
3255 'If this is due to a subscription please re-write it to use React provided hooks. ' +
3256 'Otherwise concurrent mode guarantees are off the table.',
3257 );
3258 }
3259 }
3260 }
3261 }
3262 }
3263
3264 const noop = () => {};
3265
3266 export function startHostTransition<F>(
3267 formFiber: Fiber,
3268 pendingState: TransitionStatus,
3269 action: (F => mixed) | null,
3270 formData: F,
3271 ): void {
3272 if (formFiber.tag !== HostComponent) {
3273 throw new Error(
3274 'Expected the form instance to be a HostComponent. This ' +
3275 'is a bug in React.',
3276 );
3277 }
3278
3279 const stateHook = ensureFormComponentIsStateful(formFiber);
3280
3281 const queue: UpdateQueue<
3282 Thenable<TransitionStatus> | TransitionStatus,
3283 BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
3284 > = stateHook.queue;
3285
3286 startHostActionTimer(formFiber);
3287
3288 startTransition(
3289 formFiber,
3290 queue,
3291 pendingState,
3292 NoPendingHostTransition,
3293 // TODO: `startTransition` both sets the pending state and dispatches
3294 // the action, if one is provided. Consider refactoring these two
3295 // concerns to avoid the extra lambda.
3296
3297 action === null
3298 ? // No action was provided, but we still call `startTransition` to
3299 // set the pending form status.
3300 noop
3301 : () => {
3302 // Automatically reset the form when the action completes.
3303 requestFormReset(formFiber);
3304 return action(formData);
3305 },
3306 );
3307 }
3308
3309 function ensureFormComponentIsStateful(formFiber: Fiber) {
3310 const existingStateHook: Hook | null = formFiber.memoizedState;
3311 if (existingStateHook !== null) {
3312 // This fiber was already upgraded to be stateful.
3313 return existingStateHook;
3314 }
3315
3316 // Upgrade this host component fiber to be stateful. We're going to pretend
3317 // it was stateful all along so we can reuse most of the implementation
3318 // for function components and useTransition.
3319 //
3320 // Create the state hook used by TransitionAwareHostComponent. This is
3321 // essentially an inlined version of mountState.
3322 const newQueue: UpdateQueue<
3323 Thenable<TransitionStatus> | TransitionStatus,
3324 BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
3325 > = {
3326 pending: null,
3327 lanes: NoLanes,
3328 // We're going to cheat and intentionally not create a bound dispatch
3329 // method, because we can call it directly in startTransition.
3330 dispatch: null as any,
3331 lastRenderedReducer: basicStateReducer,
3332 lastRenderedState: NoPendingHostTransition,
3333 };
3334
3335 const stateHook: Hook = {
3336 memoizedState: NoPendingHostTransition,
3337 baseState: NoPendingHostTransition,
3338 baseQueue: null,
3339 queue: newQueue,
3340 next: null,
3341 };
3342
3343 // We use another state hook to track whether the form needs to be reset.
3344 // The state is an empty object. To trigger a reset, we update the state
3345 // to a new object. Then during rendering, we detect that the state has
3346 // changed and schedule a commit effect.
3347 const initialResetState = {};
3348 const newResetStateQueue: UpdateQueue<Object, Object> = {
3349 pending: null,
3350 lanes: NoLanes,
3351 // We're going to cheat and intentionally not create a bound dispatch
3352 // method, because we can call it directly in startTransition.
3353 dispatch: null as any,
3354 lastRenderedReducer: basicStateReducer,
3355 lastRenderedState: initialResetState,
3356 };
3357 const resetStateHook: Hook = {
3358 memoizedState: initialResetState,
3359 baseState: initialResetState,
3360 baseQueue: null,
3361 queue: newResetStateQueue,
3362 next: null,
3363 };
3364 stateHook.next = resetStateHook;
3365
3366 // Add the hook list to both fiber alternates. The idea is that the fiber
3367 // had this hook all along.
3368 formFiber.memoizedState = stateHook;
3369 const alternate = formFiber.alternate;
3370 if (alternate !== null) {
3371 alternate.memoizedState = stateHook;
3372 }
3373
3374 return stateHook;
3375 }
3376
3377 export function requestFormReset(formFiber: Fiber) {
3378 const transition = requestCurrentTransition();
3379
3380 if (transition === null) {
3381 if (__DEV__) {
3382 // An optimistic update occurred, but startTransition is not on the stack.
3383 // The form reset will be scheduled at default (sync) priority, which
3384 // is probably not what the user intended. Most likely because the
3385 // requestFormReset call happened after an `await`.
3386 // TODO: Theoretically, requestFormReset is still useful even for
3387 // non-transition updates because it allows you to update defaultValue
3388 // synchronously and then wait to reset until after the update commits.
3389 // I've chosen to warn anyway because it's more likely the `await` mistake
3390 // described above. But arguably we shouldn't.
3391 console.error(
3392 'requestFormReset was called outside a transition or action. To ' +
3393 'fix, move to an action, or wrap with startTransition.',
3394 );
3395 }
3396 } else if (enableGestureTransition && transition.gesture) {
3397 throw new Error(
3398 'Cannot requestFormReset() inside a startGestureTransition. ' +
3399 'There should be no side-effects associated with starting a ' +
3400 'Gesture until its Action is invoked. Move side-effects to the ' +
3401 'Action instead.',
3402 );
3403 }
3404
3405 let stateHook: Hook = ensureFormComponentIsStateful(formFiber);
3406 const newResetState = {};
3407 if (stateHook.next === null) {
3408 // Hack alert. If formFiber is the workInProgress Fiber then
3409 // we might get a broken intermediate state. Try the alternate
3410 // instead.
3411 // TODO: We should really stash the Queue somewhere stateful
3412 // just like how setState binds the Queue.
3413 stateHook = (formFiber.alternate as any).memoizedState;
3414 }
3415 const resetStateHook: Hook = stateHook.next as any;
3416 const resetStateQueue = resetStateHook.queue;
3417 dispatchSetStateInternal(
3418 formFiber,
3419 resetStateQueue,
3420 newResetState,
3421 requestUpdateLane(formFiber),
3422 );
3423 }
3424
3425 function mountTransition(): [
3426 boolean,
3427 (callback: () => void, options?: StartTransitionOptions) => void,
3428 ] {
3429 const stateHook = mountStateImpl(false as Thenable<boolean> | boolean);
3430 // The `start` method never changes.
3431 const start = startTransition.bind(
3432 null,
3433 currentlyRenderingFiber,
3434 stateHook.queue,
3435 true,
3436 false,
3437 );
3438 const hook = mountWorkInProgressHook();
3439 hook.memoizedState = start;
3440 return [false, start];
3441 }
3442
3443 function updateTransition(): [
3444 boolean,
3445 (callback: () => void, options?: StartTransitionOptions) => void,
3446 ] {
3447 const [booleanOrThenable] = updateState(false);
3448 const hook = updateWorkInProgressHook();
3449 const start = hook.memoizedState;
3450 const isPending =
3451 typeof booleanOrThenable === 'boolean'
3452 ? booleanOrThenable
3453 : // This will suspend until the async action scope has finished.
3454 useThenable(booleanOrThenable);
3455 return [isPending, start];
3456 }
3457
3458 function rerenderTransition(): [
3459 boolean,
3460 (callback: () => void, options?: StartTransitionOptions) => void,
3461 ] {
3462 const [booleanOrThenable] = rerenderState(false);
3463 const hook = updateWorkInProgressHook();
3464 const start = hook.memoizedState;
3465 const isPending =
3466 typeof booleanOrThenable === 'boolean'
3467 ? booleanOrThenable
3468 : // This will suspend until the async action scope has finished.
3469 useThenable(booleanOrThenable);
3470 return [isPending, start];
3471 }
3472
3473 function useHostTransitionStatus(): TransitionStatus {
3474 return readContext(HostTransitionContext);
3475 }
3476
3477 function mountId(): string {
3478 const hook = mountWorkInProgressHook();
3479
3480 const root = getWorkInProgressRoot() as any as FiberRoot;
3481 // TODO: In Fizz, id generation is specific to each server config. Maybe we
3482 // should do this in Fiber, too? Deferring this decision for now because
3483 // there's no other place to store the prefix except for an internal field on
3484 // the public createRoot object, which the fiber tree does not currently have
3485 // a reference to.
3486 const identifierPrefix = root.identifierPrefix;
3487
3488 let id;
3489 if (getIsHydrating()) {
3490 const treeId = getTreeId();
3491
3492 // Use a captial R prefix for server-generated ids.
3493 id = '_' + identifierPrefix + 'R_' + treeId;
3494
3495 // Unless this is the first id at this level, append a number at the end
3496 // that represents the position of this useId hook among all the useId
3497 // hooks for this fiber.
3498 const localId = localIdCounter++;
3499 if (localId > 0) {
3500 id += 'H' + localId.toString(32);
3501 }
3502
3503 id += '_';
3504 } else {
3505 // Use a lowercase r prefix for client-generated ids.
3506 const globalClientId = globalClientIdCounter++;
3507 id = '_' + identifierPrefix + 'r_' + globalClientId.toString(32) + '_';
3508 }
3509
3510 hook.memoizedState = id;
3511 return id;
3512 }
3513
3514 function updateId(): string {
3515 const hook = updateWorkInProgressHook();
3516 const id: string = hook.memoizedState;
3517 return id;
3518 }
3519
3520 function mountRefresh(): any {
3521 const hook = mountWorkInProgressHook();
3522 const refresh = (hook.memoizedState = refreshCache.bind(
3523 null,
3524 currentlyRenderingFiber,
3525 ));
3526 return refresh;
3527 }
3528
3529 function updateRefresh(): any {
3530 const hook = updateWorkInProgressHook();
3531 return hook.memoizedState;
3532 }
3533
3534 function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
3535 // TODO: Does Cache work in legacy mode? Should decide and write a test.
3536 // TODO: Consider warning if the refresh is at discrete priority, or if we
3537 // otherwise suspect that it wasn't batched properly.
3538 let provider = fiber.return;
3539 while (provider !== null) {
3540 switch (provider.tag) {
3541 case CacheComponent:
3542 case HostRoot: {
3543 // Schedule an update on the cache boundary to trigger a refresh.
3544 const lane = requestUpdateLane(provider);
3545 const refreshUpdate = createLegacyQueueUpdate(lane);
3546 const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
3547 if (root !== null) {
3548 startUpdateTimerByLane(lane, 'refresh()', fiber);
3549 scheduleUpdateOnFiber(root, provider, lane);
3550 entangleLegacyQueueTransitions(root, provider, lane);
3551 }
3552
3553 // TODO: If a refresh never commits, the new cache created here must be
3554 // released. A simple case is start refreshing a cache boundary, but then
3555 // unmount that boundary before the refresh completes.
3556 const seededCache = createCache();
3557 if (seedKey !== null && seedKey !== undefined && root !== null) {
3558 if (enableLegacyCache) {
3559 // Seed the cache with the value passed by the caller. This could be
3560 // from a server mutation, or it could be a streaming response.
3561 seededCache.data.set(seedKey, seedValue);
3562 } else {
3563 if (__DEV__) {
3564 console.error(
3565 'The seed argument is not enabled outside experimental channels.',
3566 );
3567 }
3568 }
3569 }
3570
3571 const payload = {
3572 cache: seededCache,
3573 };
3574 refreshUpdate.payload = payload;
3575 return;
3576 }
3577 }
3578 provider = provider.return;
3579 }
3580 // TODO: Warn if unmounted?
3581 }
3582
3583 function dispatchReducerAction<S, A>(
3584 fiber: Fiber,
3585 queue: UpdateQueue<S, A>,
3586 action: A,
3587 ): void {
3588 if (__DEV__) {
3589 // using a reference to `arguments` bails out of GCC optimizations which affect function arity
3590 const args = arguments;
3591 if (typeof args[3] === 'function') {
3592 console.error(
3593 "State updates from the useState() and useReducer() Hooks don't support the " +
3594 'second callback argument. To execute a side effect after ' +
3595 'rendering, declare it in the component body with useEffect().',
3596 );
3597 }
3598 }
3599
3600 const lane = requestUpdateLane(fiber);
3601
3602 const update: Update<S, A> = {
3603 lane,
3604 revertLane: NoLane,
3605 gesture: null,
3606 action,
3607 hasEagerState: false,
3608 eagerState: null,
3609 next: null as any,
3610 };
3611
3612 if (isRenderPhaseUpdate(fiber)) {
3613 enqueueRenderPhaseUpdate(queue, update);
3614 } else {
3615 const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
3616 if (root !== null) {
3617 startUpdateTimerByLane(lane, 'dispatch()', fiber);
3618 scheduleUpdateOnFiber(root, fiber, lane);
3619 entangleTransitionUpdate(root, queue, lane);
3620 }
3621 }
3622
3623 markUpdateInDevTools(fiber, lane, action);
3624 }
3625
3626 function dispatchSetState<S, A>(
3627 fiber: Fiber,
3628 queue: UpdateQueue<S, A>,
3629 action: A,
3630 ): void {
3631 if (__DEV__) {
3632 // using a reference to `arguments` bails out of GCC optimizations which affect function arity
3633 const args = arguments;
3634 if (typeof args[3] === 'function') {
3635 console.error(
3636 "State updates from the useState() and useReducer() Hooks don't support the " +
3637 'second callback argument. To execute a side effect after ' +
3638 'rendering, declare it in the component body with useEffect().',
3639 );
3640 }
3641 }
3642
3643 const lane = requestUpdateLane(fiber);
3644 const didScheduleUpdate = dispatchSetStateInternal(
3645 fiber,
3646 queue,
3647 action,
3648 lane,
3649 );
3650 if (didScheduleUpdate) {
3651 startUpdateTimerByLane(lane, 'setState()', fiber);
3652 }
3653 markUpdateInDevTools(fiber, lane, action);
3654 }
3655
3656 function dispatchSetStateInternal<S, A>(
3657 fiber: Fiber,
3658 queue: UpdateQueue<S, A>,
3659 action: A,
3660 lane: Lane,
3661 ): boolean {
3662 const update: Update<S, A> = {
3663 lane,
3664 revertLane: NoLane,
3665 gesture: null,
3666 action,
3667 hasEagerState: false,
3668 eagerState: null,
3669 next: null as any,
3670 };
3671
3672 if (isRenderPhaseUpdate(fiber)) {
3673 enqueueRenderPhaseUpdate(queue, update);
3674 } else {
3675 const alternate = fiber.alternate;
3676 if (
3677 fiber.lanes === NoLanes &&
3678 (alternate === null || alternate.lanes === NoLanes)
3679 ) {
3680 // The queue is currently empty, which means we can eagerly compute the
3681 // next state before entering the render phase. If the new state is the
3682 // same as the current state, we may be able to bail out entirely.
3683 const lastRenderedReducer = queue.lastRenderedReducer;
3684 if (lastRenderedReducer !== null) {
3685 let prevDispatcher = null;
3686 if (__DEV__) {
3687 prevDispatcher = ReactSharedInternals.H;
3688 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
3689 }
3690 try {
3691 const currentState: S = queue.lastRenderedState as any;
3692 const eagerState = lastRenderedReducer(currentState, action);
3693 // Stash the eagerly computed state, and the reducer used to compute
3694 // it, on the update object. If the reducer hasn't changed by the
3695 // time we enter the render phase, then the eager state can be used
3696 // without calling the reducer again.
3697 update.hasEagerState = true;
3698 update.eagerState = eagerState;
3699 if (is(eagerState, currentState)) {
3700 // Fast path. We can bail out without scheduling React to re-render.
3701 // It's still possible that we'll need to rebase this update later,
3702 // if the component re-renders for a different reason and by that
3703 // time the reducer has changed.
3704 // TODO: Do we still need to entangle transitions in this case?
3705 enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
3706 return false;
3707 }
3708 } catch (error) {
3709 // Suppress the error. It will throw again in the render phase.
3710 } finally {
3711 if (__DEV__) {
3712 ReactSharedInternals.H = prevDispatcher;
3713 }
3714 }
3715 }
3716 }
3717
3718 const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
3719 if (root !== null) {
3720 scheduleUpdateOnFiber(root, fiber, lane);
3721 entangleTransitionUpdate(root, queue, lane);
3722 return true;
3723 }
3724 }
3725 return false;
3726 }
3727
3728 function dispatchOptimisticSetState<S, A>(
3729 fiber: Fiber,
3730 throwIfDuringRender: boolean,
3731 queue: UpdateQueue<S, A>,
3732 action: A,
3733 ): void {
3734 const transition = requestCurrentTransition();
3735
3736 if (__DEV__) {
3737 if (transition === null) {
3738 // An optimistic update occurred, but startTransition is not on the stack.
3739 // There are two likely scenarios.
3740
3741 // One possibility is that the optimistic update is triggered by a regular
3742 // event handler (e.g. `onSubmit`) instead of an action. This is a mistake
3743 // and we will warn.
3744
3745 // The other possibility is the optimistic update is inside an async
3746 // action, but after an `await`. In this case, we can make it "just work"
3747 // by associating the optimistic update with the pending async action.
3748
3749 // Technically it's possible that the optimistic update is unrelated to
3750 // the pending action, but we don't have a way of knowing this for sure
3751 // because browsers currently do not provide a way to track async scope.
3752 // (The AsyncContext proposal, if it lands, will solve this in the
3753 // future.) However, this is no different than the problem of unrelated
3754 // transitions being grouped together — it's not wrong per se, but it's
3755 // not ideal.
3756
3757 // Once AsyncContext starts landing in browsers, we will provide better
3758 // warnings in development for these cases.
3759 if (peekEntangledActionLane() !== NoLane) {
3760 // There is a pending async action. Don't warn.
3761 } else {
3762 // There's no pending async action. The most likely cause is that we're
3763 // inside a regular event handler (e.g. onSubmit) instead of an action.
3764 console.error(
3765 'An optimistic state update occurred outside a transition or ' +
3766 'action. To fix, move the update to an action, or wrap ' +
3767 'with startTransition.',
3768 );
3769 }
3770 }
3771 }
3772
3773 // For regular Transitions an optimistic update commits synchronously.
3774 // For gesture Transitions an optimistic update commits on the GestureLane.
3775 const lane =
3776 enableGestureTransition && transition !== null && transition.gesture
3777 ? GestureLane
3778 : SyncLane;
3779 const update: Update<S, A> = {
3780 lane: lane,
3781 // After committing, the optimistic update is "reverted" using the same
3782 // lane as the transition it's associated with.
3783 revertLane: requestTransitionLane(transition),
3784 gesture: null,
3785 action,
3786 hasEagerState: false,
3787 eagerState: null,
3788 next: null as any,
3789 };
3790
3791 if (isRenderPhaseUpdate(fiber)) {
3792 // When calling startTransition during render, this warns instead of
3793 // throwing because throwing would be a breaking change. setOptimisticState
3794 // is a new API so it's OK to throw.
3795 if (throwIfDuringRender) {
3796 throw new Error('Cannot update optimistic state while rendering.');
3797 } else {
3798 // startTransition was called during render. We don't need to do anything
3799 // besides warn here because the render phase update would be overidden by
3800 // the second update, anyway. We can remove this branch and make it throw
3801 // in a future release.
3802 if (__DEV__) {
3803 console.error('Cannot call startTransition while rendering.');
3804 }
3805 }
3806 } else {
3807 const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
3808 if (root !== null) {
3809 // NOTE: The optimistic update implementation assumes that the transition
3810 // will never be attempted before the optimistic update. This currently
3811 // holds because the optimistic update is always synchronous. If we ever
3812 // change that, we'll need to account for this.
3813 startUpdateTimerByLane(lane, 'setOptimistic()', fiber);
3814 scheduleUpdateOnFiber(root, fiber, lane);
3815 // Optimistic updates are always synchronous, so we don't need to call
3816 // entangleTransitionUpdate here.
3817 if (enableGestureTransition && transition !== null) {
3818 const provider = transition.gesture;
3819 if (provider !== null) {
3820 // If this was a gesture, ensure we have a scheduled gesture and that
3821 // we associate this update with this specific gesture instance.
3822 const gesture = (update.gesture = scheduleGesture(root, provider));
3823 // Ensure the gesture always uses the same revert lane. This can happen for
3824 // two startGestureTransition calls to the same provider in different events.
3825 if (gesture.revertLane === NoLane) {
3826 gesture.revertLane = update.revertLane;
3827 } else {
3828 update.revertLane = gesture.revertLane;
3829 }
3830 }
3831 }
3832 }
3833 }
3834
3835 markUpdateInDevTools(fiber, lane, action);
3836 }
3837
3838 function isRenderPhaseUpdate(fiber: Fiber): boolean {
3839 const alternate = fiber.alternate;
3840 return (
3841 fiber === currentlyRenderingFiber ||
3842 (alternate !== null && alternate === currentlyRenderingFiber)
3843 );
3844 }
3845
3846 function enqueueRenderPhaseUpdate<S, A>(
3847 queue: UpdateQueue<S, A>,
3848 update: Update<S, A>,
3849 ): void {
3850 // This is a render phase update. Stash it in a lazily-created map of
3851 // queue -> linked list of updates. After this render pass, we'll restart
3852 // and apply the stashed updates on top of the work-in-progress hook.
3853 didScheduleRenderPhaseUpdateDuringThisPass =
3854 didScheduleRenderPhaseUpdate = true;
3855 const pending = queue.pending;
3856 if (pending === null) {
3857 // This is the first update. Create a circular list.
3858 update.next = update;
3859 } else {
3860 update.next = pending.next;
3861 pending.next = update;
3862 }
3863 queue.pending = update;
3864 }
3865
3866 // TODO: Move to ReactFiberConcurrentUpdates?
3867 function entangleTransitionUpdate<S, A>(
3868 root: FiberRoot,
3869 queue: UpdateQueue<S, A>,
3870 lane: Lane,
3871 ): void {
3872 if (isTransitionLane(lane)) {
3873 let queueLanes = queue.lanes;
3874
3875 // If any entangled lanes are no longer pending on the root, then they
3876 // must have finished. We can remove them from the shared queue, which
3877 // represents a superset of the actually pending lanes. In some cases we
3878 // may entangle more than we need to, but that's OK. In fact it's worse if
3879 // we *don't* entangle when we should.
3880 queueLanes = intersectLanes(queueLanes, root.pendingLanes);
3881
3882 // Entangle the new transition lane with the other transition lanes.
3883 const newQueueLanes = mergeLanes(queueLanes, lane);
3884 queue.lanes = newQueueLanes;
3885 // Even if queue.lanes already include lane, we don't know for certain if
3886 // the lane finished since the last time we entangled it. So we need to
3887 // entangle it again, just to be sure.
3888 markRootEntangled(root, newQueueLanes);
3889 }
3890 }
3891
3892 function markUpdateInDevTools<A>(fiber: Fiber, lane: Lane, action: A): void {
3893 if (enableSchedulingProfiler) {
3894 markStateUpdateScheduled(fiber, lane);
3895 }
3896 }
3897
3898 export const ContextOnlyDispatcher: Dispatcher = {
3899 readContext,
3900
3901 use,
3902 useCallback: throwInvalidHookError,
3903 useContext: throwInvalidHookError,
3904 useEffect: throwInvalidHookError,
3905 useImperativeHandle: throwInvalidHookError,
3906 useLayoutEffect: throwInvalidHookError,
3907 useInsertionEffect: throwInvalidHookError,
3908 useMemo: throwInvalidHookError,
3909 useReducer: throwInvalidHookError,
3910 useRef: throwInvalidHookError,
3911 useState: throwInvalidHookError,
3912 useDebugValue: throwInvalidHookError,
3913 useDeferredValue: throwInvalidHookError,
3914 useTransition: throwInvalidHookError,
3915 useSyncExternalStore: throwInvalidHookError,
3916 useId: throwInvalidHookError,
3917 useHostTransitionStatus: throwInvalidHookError,
3918 useFormState: throwInvalidHookError,
3919 useActionState: throwInvalidHookError,
3920 useOptimistic: throwInvalidHookError,
3921 useMemoCache: throwInvalidHookError,
3922 useCacheRefresh: throwInvalidHookError,
3923 useEffectEvent: throwInvalidHookError,
3924 };
3925
3926 const HooksDispatcherOnMount: Dispatcher = {
3927 readContext,
3928
3929 use,
3930 useCallback: mountCallback,
3931 useContext: readContext,
3932 useEffect: mountEffect,
3933 useImperativeHandle: mountImperativeHandle,
3934 useLayoutEffect: mountLayoutEffect,
3935 useInsertionEffect: mountInsertionEffect,
3936 useMemo: mountMemo,
3937 useReducer: mountReducer,
3938 useRef: mountRef,
3939 useState: mountState,
3940 useDebugValue: mountDebugValue,
3941 useDeferredValue: mountDeferredValue,
3942 useTransition: mountTransition,
3943 useSyncExternalStore: mountSyncExternalStore,
3944 useId: mountId,
3945 useHostTransitionStatus: useHostTransitionStatus,
3946 useFormState: mountActionState,
3947 useActionState: mountActionState,
3948 useOptimistic: mountOptimistic,
3949 useMemoCache,
3950 useCacheRefresh: mountRefresh,
3951 useEffectEvent: mountEvent,
3952 };
3953
3954 const HooksDispatcherOnUpdate: Dispatcher = {
3955 readContext,
3956
3957 use,
3958 useCallback: updateCallback,
3959 useContext: readContext,
3960 useEffect: updateEffect,
3961 useImperativeHandle: updateImperativeHandle,
3962 useInsertionEffect: updateInsertionEffect,
3963 useLayoutEffect: updateLayoutEffect,
3964 useMemo: updateMemo,
3965 useReducer: updateReducer,
3966 useRef: updateRef,
3967 useState: updateState,
3968 useDebugValue: updateDebugValue,
3969 useDeferredValue: updateDeferredValue,
3970 useTransition: updateTransition,
3971 useSyncExternalStore: updateSyncExternalStore,
3972 useId: updateId,
3973 useHostTransitionStatus: useHostTransitionStatus,
3974 useFormState: updateActionState,
3975 useActionState: updateActionState,
3976 useOptimistic: updateOptimistic,
3977 useMemoCache,
3978 useCacheRefresh: updateRefresh,
3979 useEffectEvent: updateEvent,
3980 };
3981
3982 const HooksDispatcherOnRerender: Dispatcher = {
3983 readContext,
3984
3985 use,
3986 useCallback: updateCallback,
3987 useContext: readContext,
3988 useEffect: updateEffect,
3989 useImperativeHandle: updateImperativeHandle,
3990 useInsertionEffect: updateInsertionEffect,
3991 useLayoutEffect: updateLayoutEffect,
3992 useMemo: updateMemo,
3993 useReducer: rerenderReducer,
3994 useRef: updateRef,
3995 useState: rerenderState,
3996 useDebugValue: updateDebugValue,
3997 useDeferredValue: rerenderDeferredValue,
3998 useTransition: rerenderTransition,
3999 useSyncExternalStore: updateSyncExternalStore,
4000 useId: updateId,
4001 useHostTransitionStatus: useHostTransitionStatus,
4002 useFormState: rerenderActionState,
4003 useActionState: rerenderActionState,
4004 useOptimistic: rerenderOptimistic,
4005 useMemoCache,
4006 useCacheRefresh: updateRefresh,
4007 useEffectEvent: updateEvent,
4008 };
4009
4010 let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
4011 let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
4012 let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
4013 let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
4014 let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
4015 let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
4016 let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
4017
4018 if (__DEV__) {
4019 const warnInvalidContextAccess = () => {
4020 console.error(
4021 'Context can only be read while React is rendering. ' +
4022 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
4023 'In function components, you can read it directly in the function body, but not ' +
4024 'inside Hooks like useReducer() or useMemo().',
4025 );
4026 };
4027
4028 const warnInvalidHookAccess = () => {
4029 console.error(
4030 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
4031 'You can only call Hooks at the top level of your React function. ' +
4032 'For more information, see ' +
4033 'https://react.dev/link/rules-of-hooks',
4034 );
4035 };
4036
4037 HooksDispatcherOnMountInDEV = {
4038 readContext<T>(context: ReactContext<T>): T {
4039 return readContext(context);
4040 },
4041 use,
4042 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4043 currentHookNameInDev = 'useCallback';
4044 mountHookTypesDev();
4045 checkDepsAreArrayDev(deps);
4046 return mountCallback(callback, deps);
4047 },
4048 useContext<T>(context: ReactContext<T>): T {
4049 currentHookNameInDev = 'useContext';
4050 mountHookTypesDev();
4051 return readContext(context);
4052 },
4053 useEffect(
4054 create: () => (() => void) | void,
4055 deps: Array<mixed> | void | null,
4056 ): void {
4057 currentHookNameInDev = 'useEffect';
4058 mountHookTypesDev();
4059 checkDepsAreArrayDev(deps);
4060 return mountEffect(create, deps);
4061 },
4062 useImperativeHandle<T>(
4063 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4064 create: () => T,
4065 deps: Array<mixed> | void | null,
4066 ): void {
4067 currentHookNameInDev = 'useImperativeHandle';
4068 mountHookTypesDev();
4069 checkDepsAreArrayDev(deps);
4070 return mountImperativeHandle(ref, create, deps);
4071 },
4072 useInsertionEffect(
4073 create: () => (() => void) | void,
4074 deps: Array<mixed> | void | null,
4075 ): void {
4076 currentHookNameInDev = 'useInsertionEffect';
4077 mountHookTypesDev();
4078 checkDepsAreArrayDev(deps);
4079 return mountInsertionEffect(create, deps);
4080 },
4081 useLayoutEffect(
4082 create: () => (() => void) | void,
4083 deps: Array<mixed> | void | null,
4084 ): void {
4085 currentHookNameInDev = 'useLayoutEffect';
4086 mountHookTypesDev();
4087 checkDepsAreArrayDev(deps);
4088 return mountLayoutEffect(create, deps);
4089 },
4090 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4091 currentHookNameInDev = 'useMemo';
4092 mountHookTypesDev();
4093 checkDepsAreArrayDev(deps);
4094 const prevDispatcher = ReactSharedInternals.H;
4095 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4096 try {
4097 return mountMemo(create, deps);
4098 } finally {
4099 ReactSharedInternals.H = prevDispatcher;
4100 }
4101 },
4102 useReducer<S, I, A>(
4103 reducer: (S, A) => S,
4104 initialArg: I,
4105 init?: I => S,
4106 ): [S, Dispatch<A>] {
4107 currentHookNameInDev = 'useReducer';
4108 mountHookTypesDev();
4109 const prevDispatcher = ReactSharedInternals.H;
4110 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4111 try {
4112 return mountReducer(reducer, initialArg, init);
4113 } finally {
4114 ReactSharedInternals.H = prevDispatcher;
4115 }
4116 },
4117 useRef<T>(initialValue: T): {current: T} {
4118 currentHookNameInDev = 'useRef';
4119 mountHookTypesDev();
4120 return mountRef(initialValue);
4121 },
4122 useState<S>(
4123 initialState: (() => S) | S,
4124 ): [S, Dispatch<BasicStateAction<S>>] {
4125 currentHookNameInDev = 'useState';
4126 mountHookTypesDev();
4127 const prevDispatcher = ReactSharedInternals.H;
4128 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4129 try {
4130 return mountState(initialState);
4131 } finally {
4132 ReactSharedInternals.H = prevDispatcher;
4133 }
4134 },
4135 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4136 currentHookNameInDev = 'useDebugValue';
4137 mountHookTypesDev();
4138 return mountDebugValue(value, formatterFn);
4139 },
4140 useDeferredValue<T>(value: T, initialValue?: T): T {
4141 currentHookNameInDev = 'useDeferredValue';
4142 mountHookTypesDev();
4143 return mountDeferredValue(value, initialValue);
4144 },
4145 useTransition(): [boolean, (() => void) => void] {
4146 currentHookNameInDev = 'useTransition';
4147 mountHookTypesDev();
4148 return mountTransition();
4149 },
4150 useSyncExternalStore<T>(
4151 subscribe: (() => void) => () => void,
4152 getSnapshot: () => T,
4153 getServerSnapshot?: () => T,
4154 ): T {
4155 currentHookNameInDev = 'useSyncExternalStore';
4156 mountHookTypesDev();
4157 return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4158 },
4159 useId(): string {
4160 currentHookNameInDev = 'useId';
4161 mountHookTypesDev();
4162 return mountId();
4163 },
4164 useFormState<S, P>(
4165 action: (Awaited<S>, P) => S,
4166 initialState: Awaited<S>,
4167 permalink?: string,
4168 ): [Awaited<S>, (P) => void, boolean] {
4169 currentHookNameInDev = 'useFormState';
4170 mountHookTypesDev();
4171 warnOnUseFormStateInDev();
4172 return mountActionState(action, initialState, permalink);
4173 },
4174 useActionState<S, P>(
4175 action: (Awaited<S>, P) => S,
4176 initialState: Awaited<S>,
4177 permalink?: string,
4178 ): [Awaited<S>, (P) => void, boolean] {
4179 currentHookNameInDev = 'useActionState';
4180 mountHookTypesDev();
4181 return mountActionState(action, initialState, permalink);
4182 },
4183 useOptimistic<S, A>(
4184 passthrough: S,
4185 reducer: ?(S, A) => S,
4186 ): [S, (A) => void] {
4187 currentHookNameInDev = 'useOptimistic';
4188 mountHookTypesDev();
4189 return mountOptimistic(passthrough, reducer);
4190 },
4191 useHostTransitionStatus,
4192 useMemoCache,
4193 useCacheRefresh() {
4194 currentHookNameInDev = 'useCacheRefresh';
4195 mountHookTypesDev();
4196 return mountRefresh();
4197 },
4198 useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
4199 callback: F,
4200 ): F {
4201 currentHookNameInDev = 'useEffectEvent';
4202 mountHookTypesDev();
4203 return mountEvent(callback);
4204 },
4205 };
4206
4207 HooksDispatcherOnMountWithHookTypesInDEV = {
4208 readContext<T>(context: ReactContext<T>): T {
4209 return readContext(context);
4210 },
4211 use,
4212 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4213 currentHookNameInDev = 'useCallback';
4214 updateHookTypesDev();
4215 return mountCallback(callback, deps);
4216 },
4217 useContext<T>(context: ReactContext<T>): T {
4218 currentHookNameInDev = 'useContext';
4219 updateHookTypesDev();
4220 return readContext(context);
4221 },
4222 useEffect(
4223 create: () => (() => void) | void,
4224 deps: Array<mixed> | void | null,
4225 ): void {
4226 currentHookNameInDev = 'useEffect';
4227 updateHookTypesDev();
4228 return mountEffect(create, deps);
4229 },
4230 useImperativeHandle<T>(
4231 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4232 create: () => T,
4233 deps: Array<mixed> | void | null,
4234 ): void {
4235 currentHookNameInDev = 'useImperativeHandle';
4236 updateHookTypesDev();
4237 return mountImperativeHandle(ref, create, deps);
4238 },
4239 useInsertionEffect(
4240 create: () => (() => void) | void,
4241 deps: Array<mixed> | void | null,
4242 ): void {
4243 currentHookNameInDev = 'useInsertionEffect';
4244 updateHookTypesDev();
4245 return mountInsertionEffect(create, deps);
4246 },
4247 useLayoutEffect(
4248 create: () => (() => void) | void,
4249 deps: Array<mixed> | void | null,
4250 ): void {
4251 currentHookNameInDev = 'useLayoutEffect';
4252 updateHookTypesDev();
4253 return mountLayoutEffect(create, deps);
4254 },
4255 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4256 currentHookNameInDev = 'useMemo';
4257 updateHookTypesDev();
4258 const prevDispatcher = ReactSharedInternals.H;
4259 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4260 try {
4261 return mountMemo(create, deps);
4262 } finally {
4263 ReactSharedInternals.H = prevDispatcher;
4264 }
4265 },
4266 useReducer<S, I, A>(
4267 reducer: (S, A) => S,
4268 initialArg: I,
4269 init?: I => S,
4270 ): [S, Dispatch<A>] {
4271 currentHookNameInDev = 'useReducer';
4272 updateHookTypesDev();
4273 const prevDispatcher = ReactSharedInternals.H;
4274 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4275 try {
4276 return mountReducer(reducer, initialArg, init);
4277 } finally {
4278 ReactSharedInternals.H = prevDispatcher;
4279 }
4280 },
4281 useRef<T>(initialValue: T): {current: T} {
4282 currentHookNameInDev = 'useRef';
4283 updateHookTypesDev();
4284 return mountRef(initialValue);
4285 },
4286 useState<S>(
4287 initialState: (() => S) | S,
4288 ): [S, Dispatch<BasicStateAction<S>>] {
4289 currentHookNameInDev = 'useState';
4290 updateHookTypesDev();
4291 const prevDispatcher = ReactSharedInternals.H;
4292 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4293 try {
4294 return mountState(initialState);
4295 } finally {
4296 ReactSharedInternals.H = prevDispatcher;
4297 }
4298 },
4299 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4300 currentHookNameInDev = 'useDebugValue';
4301 updateHookTypesDev();
4302 return mountDebugValue(value, formatterFn);
4303 },
4304 useDeferredValue<T>(value: T, initialValue?: T): T {
4305 currentHookNameInDev = 'useDeferredValue';
4306 updateHookTypesDev();
4307 return mountDeferredValue(value, initialValue);
4308 },
4309 useTransition(): [boolean, (() => void) => void] {
4310 currentHookNameInDev = 'useTransition';
4311 updateHookTypesDev();
4312 return mountTransition();
4313 },
4314 useSyncExternalStore<T>(
4315 subscribe: (() => void) => () => void,
4316 getSnapshot: () => T,
4317 getServerSnapshot?: () => T,
4318 ): T {
4319 currentHookNameInDev = 'useSyncExternalStore';
4320 updateHookTypesDev();
4321 return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4322 },
4323 useId(): string {
4324 currentHookNameInDev = 'useId';
4325 updateHookTypesDev();
4326 return mountId();
4327 },
4328 useActionState<S, P>(
4329 action: (Awaited<S>, P) => S,
4330 initialState: Awaited<S>,
4331 permalink?: string,
4332 ): [Awaited<S>, (P) => void, boolean] {
4333 currentHookNameInDev = 'useActionState';
4334 updateHookTypesDev();
4335 return mountActionState(action, initialState, permalink);
4336 },
4337 useFormState<S, P>(
4338 action: (Awaited<S>, P) => S,
4339 initialState: Awaited<S>,
4340 permalink?: string,
4341 ): [Awaited<S>, (P) => void, boolean] {
4342 currentHookNameInDev = 'useFormState';
4343 updateHookTypesDev();
4344 warnOnUseFormStateInDev();
4345 return mountActionState(action, initialState, permalink);
4346 },
4347 useOptimistic<S, A>(
4348 passthrough: S,
4349 reducer: ?(S, A) => S,
4350 ): [S, (A) => void] {
4351 currentHookNameInDev = 'useOptimistic';
4352 updateHookTypesDev();
4353 return mountOptimistic(passthrough, reducer);
4354 },
4355 useHostTransitionStatus,
4356 useMemoCache,
4357 useCacheRefresh() {
4358 currentHookNameInDev = 'useCacheRefresh';
4359 updateHookTypesDev();
4360 return mountRefresh();
4361 },
4362 useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
4363 callback: F,
4364 ): F {
4365 currentHookNameInDev = 'useEffectEvent';
4366 updateHookTypesDev();
4367 return mountEvent(callback);
4368 },
4369 };
4370
4371 HooksDispatcherOnUpdateInDEV = {
4372 readContext<T>(context: ReactContext<T>): T {
4373 return readContext(context);
4374 },
4375 use,
4376 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4377 currentHookNameInDev = 'useCallback';
4378 updateHookTypesDev();
4379 return updateCallback(callback, deps);
4380 },
4381 useContext<T>(context: ReactContext<T>): T {
4382 currentHookNameInDev = 'useContext';
4383 updateHookTypesDev();
4384 return readContext(context);
4385 },
4386 useEffect(
4387 create: () => (() => void) | void,
4388 deps: Array<mixed> | void | null,
4389 ): void {
4390 currentHookNameInDev = 'useEffect';
4391 updateHookTypesDev();
4392 return updateEffect(create, deps);
4393 },
4394 useImperativeHandle<T>(
4395 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4396 create: () => T,
4397 deps: Array<mixed> | void | null,
4398 ): void {
4399 currentHookNameInDev = 'useImperativeHandle';
4400 updateHookTypesDev();
4401 return updateImperativeHandle(ref, create, deps);
4402 },
4403 useInsertionEffect(
4404 create: () => (() => void) | void,
4405 deps: Array<mixed> | void | null,
4406 ): void {
4407 currentHookNameInDev = 'useInsertionEffect';
4408 updateHookTypesDev();
4409 return updateInsertionEffect(create, deps);
4410 },
4411 useLayoutEffect(
4412 create: () => (() => void) | void,
4413 deps: Array<mixed> | void | null,
4414 ): void {
4415 currentHookNameInDev = 'useLayoutEffect';
4416 updateHookTypesDev();
4417 return updateLayoutEffect(create, deps);
4418 },
4419 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4420 currentHookNameInDev = 'useMemo';
4421 updateHookTypesDev();
4422 const prevDispatcher = ReactSharedInternals.H;
4423 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4424 try {
4425 return updateMemo(create, deps);
4426 } finally {
4427 ReactSharedInternals.H = prevDispatcher;
4428 }
4429 },
4430 useReducer<S, I, A>(
4431 reducer: (S, A) => S,
4432 initialArg: I,
4433 init?: I => S,
4434 ): [S, Dispatch<A>] {
4435 currentHookNameInDev = 'useReducer';
4436 updateHookTypesDev();
4437 const prevDispatcher = ReactSharedInternals.H;
4438 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4439 try {
4440 return updateReducer(reducer, initialArg, init);
4441 } finally {
4442 ReactSharedInternals.H = prevDispatcher;
4443 }
4444 },
4445 useRef<T>(initialValue: T): {current: T} {
4446 currentHookNameInDev = 'useRef';
4447 updateHookTypesDev();
4448 return updateRef(initialValue);
4449 },
4450 useState<S>(
4451 initialState: (() => S) | S,
4452 ): [S, Dispatch<BasicStateAction<S>>] {
4453 currentHookNameInDev = 'useState';
4454 updateHookTypesDev();
4455 const prevDispatcher = ReactSharedInternals.H;
4456 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4457 try {
4458 return updateState(initialState);
4459 } finally {
4460 ReactSharedInternals.H = prevDispatcher;
4461 }
4462 },
4463 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4464 currentHookNameInDev = 'useDebugValue';
4465 updateHookTypesDev();
4466 return updateDebugValue(value, formatterFn);
4467 },
4468 useDeferredValue<T>(value: T, initialValue?: T): T {
4469 currentHookNameInDev = 'useDeferredValue';
4470 updateHookTypesDev();
4471 return updateDeferredValue(value, initialValue);
4472 },
4473 useTransition(): [boolean, (() => void) => void] {
4474 currentHookNameInDev = 'useTransition';
4475 updateHookTypesDev();
4476 return updateTransition();
4477 },
4478 useSyncExternalStore<T>(
4479 subscribe: (() => void) => () => void,
4480 getSnapshot: () => T,
4481 getServerSnapshot?: () => T,
4482 ): T {
4483 currentHookNameInDev = 'useSyncExternalStore';
4484 updateHookTypesDev();
4485 return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4486 },
4487 useId(): string {
4488 currentHookNameInDev = 'useId';
4489 updateHookTypesDev();
4490 return updateId();
4491 },
4492 useFormState<S, P>(
4493 action: (Awaited<S>, P) => S,
4494 initialState: Awaited<S>,
4495 permalink?: string,
4496 ): [Awaited<S>, (P) => void, boolean] {
4497 currentHookNameInDev = 'useFormState';
4498 updateHookTypesDev();
4499 warnOnUseFormStateInDev();
4500 return updateActionState(action, initialState, permalink);
4501 },
4502 useActionState<S, P>(
4503 action: (Awaited<S>, P) => S,
4504 initialState: Awaited<S>,
4505 permalink?: string,
4506 ): [Awaited<S>, (P) => void, boolean] {
4507 currentHookNameInDev = 'useActionState';
4508 updateHookTypesDev();
4509 return updateActionState(action, initialState, permalink);
4510 },
4511 useOptimistic<S, A>(
4512 passthrough: S,
4513 reducer: ?(S, A) => S,
4514 ): [S, (A) => void] {
4515 currentHookNameInDev = 'useOptimistic';
4516 updateHookTypesDev();
4517 return updateOptimistic(passthrough, reducer);
4518 },
4519 useHostTransitionStatus,
4520 useMemoCache,
4521 useCacheRefresh() {
4522 currentHookNameInDev = 'useCacheRefresh';
4523 updateHookTypesDev();
4524 return updateRefresh();
4525 },
4526 useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
4527 callback: F,
4528 ): F {
4529 currentHookNameInDev = 'useEffectEvent';
4530 updateHookTypesDev();
4531 return updateEvent(callback);
4532 },
4533 };
4534
4535 HooksDispatcherOnRerenderInDEV = {
4536 readContext<T>(context: ReactContext<T>): T {
4537 return readContext(context);
4538 },
4539 use,
4540 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4541 currentHookNameInDev = 'useCallback';
4542 updateHookTypesDev();
4543 return updateCallback(callback, deps);
4544 },
4545 useContext<T>(context: ReactContext<T>): T {
4546 currentHookNameInDev = 'useContext';
4547 updateHookTypesDev();
4548 return readContext(context);
4549 },
4550 useEffect(
4551 create: () => (() => void) | void,
4552 deps: Array<mixed> | void | null,
4553 ): void {
4554 currentHookNameInDev = 'useEffect';
4555 updateHookTypesDev();
4556 return updateEffect(create, deps);
4557 },
4558 useImperativeHandle<T>(
4559 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4560 create: () => T,
4561 deps: Array<mixed> | void | null,
4562 ): void {
4563 currentHookNameInDev = 'useImperativeHandle';
4564 updateHookTypesDev();
4565 return updateImperativeHandle(ref, create, deps);
4566 },
4567 useInsertionEffect(
4568 create: () => (() => void) | void,
4569 deps: Array<mixed> | void | null,
4570 ): void {
4571 currentHookNameInDev = 'useInsertionEffect';
4572 updateHookTypesDev();
4573 return updateInsertionEffect(create, deps);
4574 },
4575 useLayoutEffect(
4576 create: () => (() => void) | void,
4577 deps: Array<mixed> | void | null,
4578 ): void {
4579 currentHookNameInDev = 'useLayoutEffect';
4580 updateHookTypesDev();
4581 return updateLayoutEffect(create, deps);
4582 },
4583 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4584 currentHookNameInDev = 'useMemo';
4585 updateHookTypesDev();
4586 const prevDispatcher = ReactSharedInternals.H;
4587 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
4588 try {
4589 return updateMemo(create, deps);
4590 } finally {
4591 ReactSharedInternals.H = prevDispatcher;
4592 }
4593 },
4594 useReducer<S, I, A>(
4595 reducer: (S, A) => S,
4596 initialArg: I,
4597 init?: I => S,
4598 ): [S, Dispatch<A>] {
4599 currentHookNameInDev = 'useReducer';
4600 updateHookTypesDev();
4601 const prevDispatcher = ReactSharedInternals.H;
4602 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
4603 try {
4604 return rerenderReducer(reducer, initialArg, init);
4605 } finally {
4606 ReactSharedInternals.H = prevDispatcher;
4607 }
4608 },
4609 useRef<T>(initialValue: T): {current: T} {
4610 currentHookNameInDev = 'useRef';
4611 updateHookTypesDev();
4612 return updateRef(initialValue);
4613 },
4614 useState<S>(
4615 initialState: (() => S) | S,
4616 ): [S, Dispatch<BasicStateAction<S>>] {
4617 currentHookNameInDev = 'useState';
4618 updateHookTypesDev();
4619 const prevDispatcher = ReactSharedInternals.H;
4620 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnRerenderInDEV;
4621 try {
4622 return rerenderState(initialState);
4623 } finally {
4624 ReactSharedInternals.H = prevDispatcher;
4625 }
4626 },
4627 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4628 currentHookNameInDev = 'useDebugValue';
4629 updateHookTypesDev();
4630 return updateDebugValue(value, formatterFn);
4631 },
4632 useDeferredValue<T>(value: T, initialValue?: T): T {
4633 currentHookNameInDev = 'useDeferredValue';
4634 updateHookTypesDev();
4635 return rerenderDeferredValue(value, initialValue);
4636 },
4637 useTransition(): [boolean, (() => void) => void] {
4638 currentHookNameInDev = 'useTransition';
4639 updateHookTypesDev();
4640 return rerenderTransition();
4641 },
4642 useSyncExternalStore<T>(
4643 subscribe: (() => void) => () => void,
4644 getSnapshot: () => T,
4645 getServerSnapshot?: () => T,
4646 ): T {
4647 currentHookNameInDev = 'useSyncExternalStore';
4648 updateHookTypesDev();
4649 return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4650 },
4651 useId(): string {
4652 currentHookNameInDev = 'useId';
4653 updateHookTypesDev();
4654 return updateId();
4655 },
4656 useFormState<S, P>(
4657 action: (Awaited<S>, P) => S,
4658 initialState: Awaited<S>,
4659 permalink?: string,
4660 ): [Awaited<S>, (P) => void, boolean] {
4661 currentHookNameInDev = 'useFormState';
4662 updateHookTypesDev();
4663 warnOnUseFormStateInDev();
4664 return rerenderActionState(action, initialState, permalink);
4665 },
4666 useActionState<S, P>(
4667 action: (Awaited<S>, P) => S,
4668 initialState: Awaited<S>,
4669 permalink?: string,
4670 ): [Awaited<S>, (P) => void, boolean] {
4671 currentHookNameInDev = 'useActionState';
4672 updateHookTypesDev();
4673 return rerenderActionState(action, initialState, permalink);
4674 },
4675 useOptimistic<S, A>(
4676 passthrough: S,
4677 reducer: ?(S, A) => S,
4678 ): [S, (A) => void] {
4679 currentHookNameInDev = 'useOptimistic';
4680 updateHookTypesDev();
4681 return rerenderOptimistic(passthrough, reducer);
4682 },
4683 useHostTransitionStatus,
4684 useMemoCache,
4685 useCacheRefresh() {
4686 currentHookNameInDev = 'useCacheRefresh';
4687 updateHookTypesDev();
4688 return updateRefresh();
4689 },
4690 useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
4691 callback: F,
4692 ): F {
4693 currentHookNameInDev = 'useEffectEvent';
4694 updateHookTypesDev();
4695 return updateEvent(callback);
4696 },
4697 };
4698
4699 InvalidNestedHooksDispatcherOnMountInDEV = {
4700 readContext<T>(context: ReactContext<T>): T {
4701 warnInvalidContextAccess();
4702 return readContext(context);
4703 },
4704 use<T>(usable: Usable<T>): T {
4705 warnInvalidHookAccess();
4706 return use(usable);
4707 },
4708 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4709 currentHookNameInDev = 'useCallback';
4710 warnInvalidHookAccess();
4711 mountHookTypesDev();
4712 return mountCallback(callback, deps);
4713 },
4714 useContext<T>(context: ReactContext<T>): T {
4715 currentHookNameInDev = 'useContext';
4716 warnInvalidHookAccess();
4717 mountHookTypesDev();
4718 return readContext(context);
4719 },
4720 useEffect(
4721 create: () => (() => void) | void,
4722 deps: Array<mixed> | void | null,
4723 ): void {
4724 currentHookNameInDev = 'useEffect';
4725 warnInvalidHookAccess();
4726 mountHookTypesDev();
4727 return mountEffect(create, deps);
4728 },
4729 useImperativeHandle<T>(
4730 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4731 create: () => T,
4732 deps: Array<mixed> | void | null,
4733 ): void {
4734 currentHookNameInDev = 'useImperativeHandle';
4735 warnInvalidHookAccess();
4736 mountHookTypesDev();
4737 return mountImperativeHandle(ref, create, deps);
4738 },
4739 useInsertionEffect(
4740 create: () => (() => void) | void,
4741 deps: Array<mixed> | void | null,
4742 ): void {
4743 currentHookNameInDev = 'useInsertionEffect';
4744 warnInvalidHookAccess();
4745 mountHookTypesDev();
4746 return mountInsertionEffect(create, deps);
4747 },
4748 useLayoutEffect(
4749 create: () => (() => void) | void,
4750 deps: Array<mixed> | void | null,
4751 ): void {
4752 currentHookNameInDev = 'useLayoutEffect';
4753 warnInvalidHookAccess();
4754 mountHookTypesDev();
4755 return mountLayoutEffect(create, deps);
4756 },
4757 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4758 currentHookNameInDev = 'useMemo';
4759 warnInvalidHookAccess();
4760 mountHookTypesDev();
4761 const prevDispatcher = ReactSharedInternals.H;
4762 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4763 try {
4764 return mountMemo(create, deps);
4765 } finally {
4766 ReactSharedInternals.H = prevDispatcher;
4767 }
4768 },
4769 useReducer<S, I, A>(
4770 reducer: (S, A) => S,
4771 initialArg: I,
4772 init?: I => S,
4773 ): [S, Dispatch<A>] {
4774 currentHookNameInDev = 'useReducer';
4775 warnInvalidHookAccess();
4776 mountHookTypesDev();
4777 const prevDispatcher = ReactSharedInternals.H;
4778 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4779 try {
4780 return mountReducer(reducer, initialArg, init);
4781 } finally {
4782 ReactSharedInternals.H = prevDispatcher;
4783 }
4784 },
4785 useRef<T>(initialValue: T): {current: T} {
4786 currentHookNameInDev = 'useRef';
4787 warnInvalidHookAccess();
4788 mountHookTypesDev();
4789 return mountRef(initialValue);
4790 },
4791 useState<S>(
4792 initialState: (() => S) | S,
4793 ): [S, Dispatch<BasicStateAction<S>>] {
4794 currentHookNameInDev = 'useState';
4795 warnInvalidHookAccess();
4796 mountHookTypesDev();
4797 const prevDispatcher = ReactSharedInternals.H;
4798 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnMountInDEV;
4799 try {
4800 return mountState(initialState);
4801 } finally {
4802 ReactSharedInternals.H = prevDispatcher;
4803 }
4804 },
4805 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4806 currentHookNameInDev = 'useDebugValue';
4807 warnInvalidHookAccess();
4808 mountHookTypesDev();
4809 return mountDebugValue(value, formatterFn);
4810 },
4811 useDeferredValue<T>(value: T, initialValue?: T): T {
4812 currentHookNameInDev = 'useDeferredValue';
4813 warnInvalidHookAccess();
4814 mountHookTypesDev();
4815 return mountDeferredValue(value, initialValue);
4816 },
4817 useTransition(): [boolean, (() => void) => void] {
4818 currentHookNameInDev = 'useTransition';
4819 warnInvalidHookAccess();
4820 mountHookTypesDev();
4821 return mountTransition();
4822 },
4823 useSyncExternalStore<T>(
4824 subscribe: (() => void) => () => void,
4825 getSnapshot: () => T,
4826 getServerSnapshot?: () => T,
4827 ): T {
4828 currentHookNameInDev = 'useSyncExternalStore';
4829 warnInvalidHookAccess();
4830 mountHookTypesDev();
4831 return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4832 },
4833 useId(): string {
4834 currentHookNameInDev = 'useId';
4835 warnInvalidHookAccess();
4836 mountHookTypesDev();
4837 return mountId();
4838 },
4839 useFormState<S, P>(
4840 action: (Awaited<S>, P) => S,
4841 initialState: Awaited<S>,
4842 permalink?: string,
4843 ): [Awaited<S>, (P) => void, boolean] {
4844 currentHookNameInDev = 'useFormState';
4845 warnInvalidHookAccess();
4846 mountHookTypesDev();
4847 return mountActionState(action, initialState, permalink);
4848 },
4849 useActionState<S, P>(
4850 action: (Awaited<S>, P) => S,
4851 initialState: Awaited<S>,
4852 permalink?: string,
4853 ): [Awaited<S>, (P) => void, boolean] {
4854 currentHookNameInDev = 'useActionState';
4855 warnInvalidHookAccess();
4856 mountHookTypesDev();
4857 return mountActionState(action, initialState, permalink);
4858 },
4859 useOptimistic<S, A>(
4860 passthrough: S,
4861 reducer: ?(S, A) => S,
4862 ): [S, (A) => void] {
4863 currentHookNameInDev = 'useOptimistic';
4864 warnInvalidHookAccess();
4865 mountHookTypesDev();
4866 return mountOptimistic(passthrough, reducer);
4867 },
4868 useMemoCache(size: number): Array<any> {
4869 warnInvalidHookAccess();
4870 return useMemoCache(size);
4871 },
4872 useHostTransitionStatus,
4873 useCacheRefresh() {
4874 currentHookNameInDev = 'useCacheRefresh';
4875 mountHookTypesDev();
4876 return mountRefresh();
4877 },
4878 useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
4879 callback: F,
4880 ): F {
4881 currentHookNameInDev = 'useEffectEvent';
4882 warnInvalidHookAccess();
4883 mountHookTypesDev();
4884 return mountEvent(callback);
4885 },
4886 };
4887
4888 InvalidNestedHooksDispatcherOnUpdateInDEV = {
4889 readContext<T>(context: ReactContext<T>): T {
4890 warnInvalidContextAccess();
4891 return readContext(context);
4892 },
4893 use<T>(usable: Usable<T>): T {
4894 warnInvalidHookAccess();
4895 return use(usable);
4896 },
4897 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
4898 currentHookNameInDev = 'useCallback';
4899 warnInvalidHookAccess();
4900 updateHookTypesDev();
4901 return updateCallback(callback, deps);
4902 },
4903 useContext<T>(context: ReactContext<T>): T {
4904 currentHookNameInDev = 'useContext';
4905 warnInvalidHookAccess();
4906 updateHookTypesDev();
4907 return readContext(context);
4908 },
4909 useEffect(
4910 create: () => (() => void) | void,
4911 deps: Array<mixed> | void | null,
4912 ): void {
4913 currentHookNameInDev = 'useEffect';
4914 warnInvalidHookAccess();
4915 updateHookTypesDev();
4916 return updateEffect(create, deps);
4917 },
4918 useImperativeHandle<T>(
4919 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
4920 create: () => T,
4921 deps: Array<mixed> | void | null,
4922 ): void {
4923 currentHookNameInDev = 'useImperativeHandle';
4924 warnInvalidHookAccess();
4925 updateHookTypesDev();
4926 return updateImperativeHandle(ref, create, deps);
4927 },
4928 useInsertionEffect(
4929 create: () => (() => void) | void,
4930 deps: Array<mixed> | void | null,
4931 ): void {
4932 currentHookNameInDev = 'useInsertionEffect';
4933 warnInvalidHookAccess();
4934 updateHookTypesDev();
4935 return updateInsertionEffect(create, deps);
4936 },
4937 useLayoutEffect(
4938 create: () => (() => void) | void,
4939 deps: Array<mixed> | void | null,
4940 ): void {
4941 currentHookNameInDev = 'useLayoutEffect';
4942 warnInvalidHookAccess();
4943 updateHookTypesDev();
4944 return updateLayoutEffect(create, deps);
4945 },
4946 useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
4947 currentHookNameInDev = 'useMemo';
4948 warnInvalidHookAccess();
4949 updateHookTypesDev();
4950 const prevDispatcher = ReactSharedInternals.H;
4951 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4952 try {
4953 return updateMemo(create, deps);
4954 } finally {
4955 ReactSharedInternals.H = prevDispatcher;
4956 }
4957 },
4958 useReducer<S, I, A>(
4959 reducer: (S, A) => S,
4960 initialArg: I,
4961 init?: I => S,
4962 ): [S, Dispatch<A>] {
4963 currentHookNameInDev = 'useReducer';
4964 warnInvalidHookAccess();
4965 updateHookTypesDev();
4966 const prevDispatcher = ReactSharedInternals.H;
4967 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4968 try {
4969 return updateReducer(reducer, initialArg, init);
4970 } finally {
4971 ReactSharedInternals.H = prevDispatcher;
4972 }
4973 },
4974 useRef<T>(initialValue: T): {current: T} {
4975 currentHookNameInDev = 'useRef';
4976 warnInvalidHookAccess();
4977 updateHookTypesDev();
4978 return updateRef(initialValue);
4979 },
4980 useState<S>(
4981 initialState: (() => S) | S,
4982 ): [S, Dispatch<BasicStateAction<S>>] {
4983 currentHookNameInDev = 'useState';
4984 warnInvalidHookAccess();
4985 updateHookTypesDev();
4986 const prevDispatcher = ReactSharedInternals.H;
4987 ReactSharedInternals.H = InvalidNestedHooksDispatcherOnUpdateInDEV;
4988 try {
4989 return updateState(initialState);
4990 } finally {
4991 ReactSharedInternals.H = prevDispatcher;
4992 }
4993 },
4994 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
4995 currentHookNameInDev = 'useDebugValue';
4996 warnInvalidHookAccess();
4997 updateHookTypesDev();
4998 return updateDebugValue(value, formatterFn);
4999 },
5000 useDeferredValue<T>(value: T, initialValue?: T): T {
Showing first 5,000 of 5,265 lines. View raw