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 {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
    
  11. 
    
  12. import type {
    
  13.   ReactContext,
    
  14.   StartTransitionOptions,
    
  15.   Thenable,
    
  16.   Usable,
    
  17.   ReactCustomFormAction,
    
  18.   Awaited,
    
  19. } from 'shared/ReactTypes';
    
  20. 
    
  21. import type {ResumableState} from './ReactFizzConfig';
    
  22. import type {Request, Task, KeyNode} from './ReactFizzServer';
    
  23. import type {ThenableState} from './ReactFizzThenable';
    
  24. import type {TransitionStatus} from './ReactFizzConfig';
    
  25. 
    
  26. import {readContext as readContextImpl} from './ReactFizzNewContext';
    
  27. import {getTreeId} from './ReactFizzTreeContext';
    
  28. import {createThenableState, trackUsedThenable} from './ReactFizzThenable';
    
  29. 
    
  30. import {makeId, NotPendingTransition} from './ReactFizzConfig';
    
  31. import {createFastHash} from './ReactServerStreamConfig';
    
  32. 
    
  33. import {
    
  34.   enableCache,
    
  35.   enableUseEffectEventHook,
    
  36.   enableUseMemoCacheHook,
    
  37.   enableAsyncActions,
    
  38.   enableFormActions,
    
  39.   enableUseDeferredValueInitialArg,
    
  40. } from 'shared/ReactFeatureFlags';
    
  41. import is from 'shared/objectIs';
    
  42. import {
    
  43.   REACT_SERVER_CONTEXT_TYPE,
    
  44.   REACT_CONTEXT_TYPE,
    
  45.   REACT_MEMO_CACHE_SENTINEL,
    
  46. } from 'shared/ReactSymbols';
    
  47. import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
    
  48. import {getFormState} from './ReactFizzServer';
    
  49. 
    
  50. type BasicStateAction<S> = (S => S) | S;
    
  51. type Dispatch<A> = A => void;
    
  52. 
    
  53. type Update<A> = {
    
  54.   action: A,
    
  55.   next: Update<A> | null,
    
  56. };
    
  57. 
    
  58. type UpdateQueue<A> = {
    
  59.   last: Update<A> | null,
    
  60.   dispatch: any,
    
  61. };
    
  62. 
    
  63. type Hook = {
    
  64.   memoizedState: any,
    
  65.   queue: UpdateQueue<any> | null,
    
  66.   next: Hook | null,
    
  67. };
    
  68. 
    
  69. let currentlyRenderingComponent: Object | null = null;
    
  70. let currentlyRenderingTask: Task | null = null;
    
  71. let currentlyRenderingRequest: Request | null = null;
    
  72. let currentlyRenderingKeyPath: KeyNode | null = null;
    
  73. let firstWorkInProgressHook: Hook | null = null;
    
  74. let workInProgressHook: Hook | null = null;
    
  75. // Whether the work-in-progress hook is a re-rendered hook
    
  76. let isReRender: boolean = false;
    
  77. // Whether an update was scheduled during the currently executing render pass.
    
  78. let didScheduleRenderPhaseUpdate: boolean = false;
    
  79. // Counts the number of useId hooks in this component
    
  80. let localIdCounter: number = 0;
    
  81. // Chunks that should be pushed to the stream once the component
    
  82. // finishes rendering.
    
  83. // Counts the number of useFormState calls in this component
    
  84. let formStateCounter: number = 0;
    
  85. // The index of the useFormState hook that matches the one passed in at the
    
  86. // root during an MPA navigation, if any.
    
  87. let formStateMatchingIndex: number = -1;
    
  88. // Counts the number of use(thenable) calls in this component
    
  89. let thenableIndexCounter: number = 0;
    
  90. let thenableState: ThenableState | null = null;
    
  91. // Lazily created map of render-phase updates
    
  92. let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
    
  93. // Counter to prevent infinite loops.
    
  94. let numberOfReRenders: number = 0;
    
  95. const RE_RENDER_LIMIT = 25;
    
  96. 
    
  97. let isInHookUserCodeInDev = false;
    
  98. 
    
  99. // In DEV, this is the name of the currently executing primitive hook
    
  100. let currentHookNameInDev: ?string;
    
  101. 
    
  102. function resolveCurrentlyRenderingComponent(): Object {
    
  103.   if (currentlyRenderingComponent === null) {
    
  104.     throw new Error(
    
  105.       'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
    
  106.         ' one of the following reasons:\n' +
    
  107.         '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
    
  108.         '2. You might be breaking the Rules of Hooks\n' +
    
  109.         '3. You might have more than one copy of React in the same app\n' +
    
  110.         'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
    
  111.     );
    
  112.   }
    
  113. 
    
  114.   if (__DEV__) {
    
  115.     if (isInHookUserCodeInDev) {
    
  116.       console.error(
    
  117.         'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
    
  118.           'You can only call Hooks at the top level of your React function. ' +
    
  119.           'For more information, see ' +
    
  120.           'https://reactjs.org/link/rules-of-hooks',
    
  121.       );
    
  122.     }
    
  123.   }
    
  124.   return currentlyRenderingComponent;
    
  125. }
    
  126. 
    
  127. function areHookInputsEqual(
    
  128.   nextDeps: Array<mixed>,
    
  129.   prevDeps: Array<mixed> | null,
    
  130. ) {
    
  131.   if (prevDeps === null) {
    
  132.     if (__DEV__) {
    
  133.       console.error(
    
  134.         '%s received a final argument during this render, but not during ' +
    
  135.           'the previous render. Even though the final argument is optional, ' +
    
  136.           'its type cannot change between renders.',
    
  137.         currentHookNameInDev,
    
  138.       );
    
  139.     }
    
  140.     return false;
    
  141.   }
    
  142. 
    
  143.   if (__DEV__) {
    
  144.     // Don't bother comparing lengths in prod because these arrays should be
    
  145.     // passed inline.
    
  146.     if (nextDeps.length !== prevDeps.length) {
    
  147.       console.error(
    
  148.         'The final argument passed to %s changed size between renders. The ' +
    
  149.           'order and size of this array must remain constant.\n\n' +
    
  150.           'Previous: %s\n' +
    
  151.           'Incoming: %s',
    
  152.         currentHookNameInDev,
    
  153.         `[${nextDeps.join(', ')}]`,
    
  154.         `[${prevDeps.join(', ')}]`,
    
  155.       );
    
  156.     }
    
  157.   }
    
  158.   // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  159.   for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    
  160.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  161.     if (is(nextDeps[i], prevDeps[i])) {
    
  162.       continue;
    
  163.     }
    
  164.     return false;
    
  165.   }
    
  166.   return true;
    
  167. }
    
  168. 
    
  169. function createHook(): Hook {
    
  170.   if (numberOfReRenders > 0) {
    
  171.     throw new Error('Rendered more hooks than during the previous render');
    
  172.   }
    
  173.   return {
    
  174.     memoizedState: null,
    
  175.     queue: null,
    
  176.     next: null,
    
  177.   };
    
  178. }
    
  179. 
    
  180. function createWorkInProgressHook(): Hook {
    
  181.   if (workInProgressHook === null) {
    
  182.     // This is the first hook in the list
    
  183.     if (firstWorkInProgressHook === null) {
    
  184.       isReRender = false;
    
  185.       firstWorkInProgressHook = workInProgressHook = createHook();
    
  186.     } else {
    
  187.       // There's already a work-in-progress. Reuse it.
    
  188.       isReRender = true;
    
  189.       workInProgressHook = firstWorkInProgressHook;
    
  190.     }
    
  191.   } else {
    
  192.     if (workInProgressHook.next === null) {
    
  193.       isReRender = false;
    
  194.       // Append to the end of the list
    
  195.       workInProgressHook = workInProgressHook.next = createHook();
    
  196.     } else {
    
  197.       // There's already a work-in-progress. Reuse it.
    
  198.       isReRender = true;
    
  199.       workInProgressHook = workInProgressHook.next;
    
  200.     }
    
  201.   }
    
  202.   return workInProgressHook;
    
  203. }
    
  204. 
    
  205. export function prepareToUseHooks(
    
  206.   request: Request,
    
  207.   task: Task,
    
  208.   keyPath: KeyNode | null,
    
  209.   componentIdentity: Object,
    
  210.   prevThenableState: ThenableState | null,
    
  211. ): void {
    
  212.   currentlyRenderingComponent = componentIdentity;
    
  213.   currentlyRenderingTask = task;
    
  214.   currentlyRenderingRequest = request;
    
  215.   currentlyRenderingKeyPath = keyPath;
    
  216.   if (__DEV__) {
    
  217.     isInHookUserCodeInDev = false;
    
  218.   }
    
  219. 
    
  220.   // The following should have already been reset
    
  221.   // didScheduleRenderPhaseUpdate = false;
    
  222.   // firstWorkInProgressHook = null;
    
  223.   // numberOfReRenders = 0;
    
  224.   // renderPhaseUpdates = null;
    
  225.   // workInProgressHook = null;
    
  226. 
    
  227.   localIdCounter = 0;
    
  228.   formStateCounter = 0;
    
  229.   formStateMatchingIndex = -1;
    
  230.   thenableIndexCounter = 0;
    
  231.   thenableState = prevThenableState;
    
  232. }
    
  233. 
    
  234. export function finishHooks(
    
  235.   Component: any,
    
  236.   props: any,
    
  237.   children: any,
    
  238.   refOrContext: any,
    
  239. ): any {
    
  240.   // This must be called after every function component to prevent hooks from
    
  241.   // being used in classes.
    
  242. 
    
  243.   while (didScheduleRenderPhaseUpdate) {
    
  244.     // Updates were scheduled during the render phase. They are stored in
    
  245.     // the `renderPhaseUpdates` map. Call the component again, reusing the
    
  246.     // work-in-progress hooks and applying the additional updates on top. Keep
    
  247.     // restarting until no more updates are scheduled.
    
  248.     didScheduleRenderPhaseUpdate = false;
    
  249.     localIdCounter = 0;
    
  250.     formStateCounter = 0;
    
  251.     formStateMatchingIndex = -1;
    
  252.     thenableIndexCounter = 0;
    
  253.     numberOfReRenders += 1;
    
  254. 
    
  255.     // Start over from the beginning of the list
    
  256.     workInProgressHook = null;
    
  257. 
    
  258.     children = Component(props, refOrContext);
    
  259.   }
    
  260. 
    
  261.   resetHooksState();
    
  262.   return children;
    
  263. }
    
  264. 
    
  265. export function getThenableStateAfterSuspending(): null | ThenableState {
    
  266.   const state = thenableState;
    
  267.   thenableState = null;
    
  268.   return state;
    
  269. }
    
  270. 
    
  271. export function checkDidRenderIdHook(): boolean {
    
  272.   // This should be called immediately after every finishHooks call.
    
  273.   // Conceptually, it's part of the return value of finishHooks; it's only a
    
  274.   // separate function to avoid using an array tuple.
    
  275.   const didRenderIdHook = localIdCounter !== 0;
    
  276.   return didRenderIdHook;
    
  277. }
    
  278. 
    
  279. export function getFormStateCount(): number {
    
  280.   // This should be called immediately after every finishHooks call.
    
  281.   // Conceptually, it's part of the return value of finishHooks; it's only a
    
  282.   // separate function to avoid using an array tuple.
    
  283.   return formStateCounter;
    
  284. }
    
  285. export function getFormStateMatchingIndex(): number {
    
  286.   // This should be called immediately after every finishHooks call.
    
  287.   // Conceptually, it's part of the return value of finishHooks; it's only a
    
  288.   // separate function to avoid using an array tuple.
    
  289.   return formStateMatchingIndex;
    
  290. }
    
  291. 
    
  292. // Reset the internal hooks state if an error occurs while rendering a component
    
  293. export function resetHooksState(): void {
    
  294.   if (__DEV__) {
    
  295.     isInHookUserCodeInDev = false;
    
  296.   }
    
  297. 
    
  298.   currentlyRenderingComponent = null;
    
  299.   currentlyRenderingTask = null;
    
  300.   currentlyRenderingRequest = null;
    
  301.   currentlyRenderingKeyPath = null;
    
  302.   didScheduleRenderPhaseUpdate = false;
    
  303.   firstWorkInProgressHook = null;
    
  304.   numberOfReRenders = 0;
    
  305.   renderPhaseUpdates = null;
    
  306.   workInProgressHook = null;
    
  307. }
    
  308. 
    
  309. function readContext<T>(context: ReactContext<T>): T {
    
  310.   if (__DEV__) {
    
  311.     if (isInHookUserCodeInDev) {
    
  312.       console.error(
    
  313.         'Context can only be read while React is rendering. ' +
    
  314.           'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
    
  315.           'In function components, you can read it directly in the function body, but not ' +
    
  316.           'inside Hooks like useReducer() or useMemo().',
    
  317.       );
    
  318.     }
    
  319.   }
    
  320.   return readContextImpl(context);
    
  321. }
    
  322. 
    
  323. function useContext<T>(context: ReactContext<T>): T {
    
  324.   if (__DEV__) {
    
  325.     currentHookNameInDev = 'useContext';
    
  326.   }
    
  327.   resolveCurrentlyRenderingComponent();
    
  328.   return readContextImpl(context);
    
  329. }
    
  330. 
    
  331. function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
    
  332.   // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
    
  333.   return typeof action === 'function' ? action(state) : action;
    
  334. }
    
  335. 
    
  336. export function useState<S>(
    
  337.   initialState: (() => S) | S,
    
  338. ): [S, Dispatch<BasicStateAction<S>>] {
    
  339.   if (__DEV__) {
    
  340.     currentHookNameInDev = 'useState';
    
  341.   }
    
  342.   return useReducer(
    
  343.     basicStateReducer,
    
  344.     // useReducer has a special case to support lazy useState initializers
    
  345.     (initialState: any),
    
  346.   );
    
  347. }
    
  348. 
    
  349. export function useReducer<S, I, A>(
    
  350.   reducer: (S, A) => S,
    
  351.   initialArg: I,
    
  352.   init?: I => S,
    
  353. ): [S, Dispatch<A>] {
    
  354.   if (__DEV__) {
    
  355.     if (reducer !== basicStateReducer) {
    
  356.       currentHookNameInDev = 'useReducer';
    
  357.     }
    
  358.   }
    
  359.   currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
    
  360.   workInProgressHook = createWorkInProgressHook();
    
  361.   if (isReRender) {
    
  362.     // This is a re-render. Apply the new render phase updates to the previous
    
  363.     // current hook.
    
  364.     const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
    
  365.     const dispatch: Dispatch<A> = (queue.dispatch: any);
    
  366.     if (renderPhaseUpdates !== null) {
    
  367.       // Render phase updates are stored in a map of queue -> linked list
    
  368.       const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    
  369.       if (firstRenderPhaseUpdate !== undefined) {
    
  370.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  371.         renderPhaseUpdates.delete(queue);
    
  372.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  373.         let newState = workInProgressHook.memoizedState;
    
  374.         let update: Update<any> = firstRenderPhaseUpdate;
    
  375.         do {
    
  376.           // Process this render phase update. We don't have to check the
    
  377.           // priority because it will always be the same as the current
    
  378.           // render's.
    
  379.           const action = update.action;
    
  380.           if (__DEV__) {
    
  381.             isInHookUserCodeInDev = true;
    
  382.           }
    
  383.           newState = reducer(newState, action);
    
  384.           if (__DEV__) {
    
  385.             isInHookUserCodeInDev = false;
    
  386.           }
    
  387.           // $FlowFixMe[incompatible-type] we bail out when we get a null
    
  388.           update = update.next;
    
  389.         } while (update !== null);
    
  390. 
    
  391.         // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  392.         workInProgressHook.memoizedState = newState;
    
  393. 
    
  394.         return [newState, dispatch];
    
  395.       }
    
  396.     }
    
  397.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  398.     return [workInProgressHook.memoizedState, dispatch];
    
  399.   } else {
    
  400.     if (__DEV__) {
    
  401.       isInHookUserCodeInDev = true;
    
  402.     }
    
  403.     let initialState;
    
  404.     if (reducer === basicStateReducer) {
    
  405.       // Special case for `useState`.
    
  406.       initialState =
    
  407.         typeof initialArg === 'function'
    
  408.           ? ((initialArg: any): () => S)()
    
  409.           : ((initialArg: any): S);
    
  410.     } else {
    
  411.       initialState =
    
  412.         init !== undefined ? init(initialArg) : ((initialArg: any): S);
    
  413.     }
    
  414.     if (__DEV__) {
    
  415.       isInHookUserCodeInDev = false;
    
  416.     }
    
  417.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  418.     workInProgressHook.memoizedState = initialState;
    
  419.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  420.     const queue: UpdateQueue<A> = (workInProgressHook.queue = {
    
  421.       last: null,
    
  422.       dispatch: null,
    
  423.     });
    
  424.     const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    
  425.       null,
    
  426.       currentlyRenderingComponent,
    
  427.       queue,
    
  428.     ): any));
    
  429.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  430.     return [workInProgressHook.memoizedState, dispatch];
    
  431.   }
    
  432. }
    
  433. 
    
  434. function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
    
  435.   currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
    
  436.   workInProgressHook = createWorkInProgressHook();
    
  437. 
    
  438.   const nextDeps = deps === undefined ? null : deps;
    
  439. 
    
  440.   if (workInProgressHook !== null) {
    
  441.     const prevState = workInProgressHook.memoizedState;
    
  442.     if (prevState !== null) {
    
  443.       if (nextDeps !== null) {
    
  444.         const prevDeps = prevState[1];
    
  445.         if (areHookInputsEqual(nextDeps, prevDeps)) {
    
  446.           return prevState[0];
    
  447.         }
    
  448.       }
    
  449.     }
    
  450.   }
    
  451. 
    
  452.   if (__DEV__) {
    
  453.     isInHookUserCodeInDev = true;
    
  454.   }
    
  455.   const nextValue = nextCreate();
    
  456.   if (__DEV__) {
    
  457.     isInHookUserCodeInDev = false;
    
  458.   }
    
  459.   // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  460.   workInProgressHook.memoizedState = [nextValue, nextDeps];
    
  461.   return nextValue;
    
  462. }
    
  463. 
    
  464. function useRef<T>(initialValue: T): {current: T} {
    
  465.   currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
    
  466.   workInProgressHook = createWorkInProgressHook();
    
  467.   const previousRef = workInProgressHook.memoizedState;
    
  468.   if (previousRef === null) {
    
  469.     const ref = {current: initialValue};
    
  470.     if (__DEV__) {
    
  471.       Object.seal(ref);
    
  472.     }
    
  473.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  474.     workInProgressHook.memoizedState = ref;
    
  475.     return ref;
    
  476.   } else {
    
  477.     return previousRef;
    
  478.   }
    
  479. }
    
  480. 
    
  481. function dispatchAction<A>(
    
  482.   componentIdentity: Object,
    
  483.   queue: UpdateQueue<A>,
    
  484.   action: A,
    
  485. ): void {
    
  486.   if (numberOfReRenders >= RE_RENDER_LIMIT) {
    
  487.     throw new Error(
    
  488.       'Too many re-renders. React limits the number of renders to prevent ' +
    
  489.         'an infinite loop.',
    
  490.     );
    
  491.   }
    
  492. 
    
  493.   if (componentIdentity === currentlyRenderingComponent) {
    
  494.     // This is a render phase update. Stash it in a lazily-created map of
    
  495.     // queue -> linked list of updates. After this render pass, we'll restart
    
  496.     // and apply the stashed updates on top of the work-in-progress hook.
    
  497.     didScheduleRenderPhaseUpdate = true;
    
  498.     const update: Update<A> = {
    
  499.       action,
    
  500.       next: null,
    
  501.     };
    
  502.     if (renderPhaseUpdates === null) {
    
  503.       renderPhaseUpdates = new Map();
    
  504.     }
    
  505.     const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    
  506.     if (firstRenderPhaseUpdate === undefined) {
    
  507.       // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  508.       renderPhaseUpdates.set(queue, update);
    
  509.     } else {
    
  510.       // Append the update to the end of the list.
    
  511.       let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
    
  512.       while (lastRenderPhaseUpdate.next !== null) {
    
  513.         lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
    
  514.       }
    
  515.       lastRenderPhaseUpdate.next = update;
    
  516.     }
    
  517.   } else {
    
  518.     // This means an update has happened after the function component has
    
  519.     // returned. On the server this is a no-op. In React Fiber, the update
    
  520.     // would be scheduled for a future render.
    
  521.   }
    
  522. }
    
  523. 
    
  524. export function useCallback<T>(
    
  525.   callback: T,
    
  526.   deps: Array<mixed> | void | null,
    
  527. ): T {
    
  528.   return useMemo(() => callback, deps);
    
  529. }
    
  530. 
    
  531. function throwOnUseEffectEventCall() {
    
  532.   throw new Error(
    
  533.     "A function wrapped in useEffectEvent can't be called during rendering.",
    
  534.   );
    
  535. }
    
  536. 
    
  537. export function useEffectEvent<Args, Return, F: (...Array<Args>) => Return>(
    
  538.   callback: F,
    
  539. ): F {
    
  540.   // $FlowIgnore[incompatible-return]
    
  541.   return throwOnUseEffectEventCall;
    
  542. }
    
  543. 
    
  544. function useSyncExternalStore<T>(
    
  545.   subscribe: (() => void) => () => void,
    
  546.   getSnapshot: () => T,
    
  547.   getServerSnapshot?: () => T,
    
  548. ): T {
    
  549.   if (getServerSnapshot === undefined) {
    
  550.     throw new Error(
    
  551.       'Missing getServerSnapshot, which is required for ' +
    
  552.         'server-rendered content. Will revert to client rendering.',
    
  553.     );
    
  554.   }
    
  555.   return getServerSnapshot();
    
  556. }
    
  557. 
    
  558. function useDeferredValue<T>(value: T, initialValue?: T): T {
    
  559.   resolveCurrentlyRenderingComponent();
    
  560.   if (enableUseDeferredValueInitialArg) {
    
  561.     return initialValue !== undefined ? initialValue : value;
    
  562.   } else {
    
  563.     return value;
    
  564.   }
    
  565. }
    
  566. 
    
  567. function unsupportedStartTransition() {
    
  568.   throw new Error('startTransition cannot be called during server rendering.');
    
  569. }
    
  570. 
    
  571. function useTransition(): [
    
  572.   boolean,
    
  573.   (callback: () => void, options?: StartTransitionOptions) => void,
    
  574. ] {
    
  575.   resolveCurrentlyRenderingComponent();
    
  576.   return [false, unsupportedStartTransition];
    
  577. }
    
  578. 
    
  579. function useHostTransitionStatus(): TransitionStatus {
    
  580.   resolveCurrentlyRenderingComponent();
    
  581.   return NotPendingTransition;
    
  582. }
    
  583. 
    
  584. function unsupportedSetOptimisticState() {
    
  585.   throw new Error('Cannot update optimistic state while rendering.');
    
  586. }
    
  587. 
    
  588. function useOptimistic<S, A>(
    
  589.   passthrough: S,
    
  590.   reducer: ?(S, A) => S,
    
  591. ): [S, (A) => void] {
    
  592.   resolveCurrentlyRenderingComponent();
    
  593.   return [passthrough, unsupportedSetOptimisticState];
    
  594. }
    
  595. 
    
  596. function createPostbackFormStateKey(
    
  597.   permalink: string | void,
    
  598.   componentKeyPath: KeyNode | null,
    
  599.   hookIndex: number,
    
  600. ): string {
    
  601.   if (permalink !== undefined) {
    
  602.     // Don't bother to hash a permalink-based key since it's already short.
    
  603.     return 'p' + permalink;
    
  604.   } else {
    
  605.     // Append a node to the key path that represents the form state hook.
    
  606.     const keyPath: KeyNode = [componentKeyPath, null, hookIndex];
    
  607.     // Key paths are hashed to reduce the size. It does not need to be secure,
    
  608.     // and it's more important that it's fast than that it's completely
    
  609.     // collision-free.
    
  610.     const keyPathHash = createFastHash(JSON.stringify(keyPath));
    
  611.     return 'k' + keyPathHash;
    
  612.   }
    
  613. }
    
  614. 
    
  615. function useFormState<S, P>(
    
  616.   action: (Awaited<S>, P) => S,
    
  617.   initialState: Awaited<S>,
    
  618.   permalink?: string,
    
  619. ): [Awaited<S>, (P) => void] {
    
  620.   resolveCurrentlyRenderingComponent();
    
  621. 
    
  622.   // Count the number of useFormState hooks per component. We also use this to
    
  623.   // track the position of this useFormState hook relative to the other ones in
    
  624.   // this component, so we can generate a unique key for each one.
    
  625.   const formStateHookIndex = formStateCounter++;
    
  626.   const request: Request = (currentlyRenderingRequest: any);
    
  627. 
    
  628.   // $FlowIgnore[prop-missing]
    
  629.   const formAction = action.$$FORM_ACTION;
    
  630.   if (typeof formAction === 'function') {
    
  631.     // This is a server action. These have additional features to enable
    
  632.     // MPA-style form submissions with progressive enhancement.
    
  633. 
    
  634.     // TODO: If the same permalink is passed to multiple useFormStates, and
    
  635.     // they all have the same action signature, Fizz will pass the postback
    
  636.     // state to all of them. We should probably only pass it to the first one,
    
  637.     // and/or warn.
    
  638. 
    
  639.     // The key is lazily generated and deduped so the that the keypath doesn't
    
  640.     // get JSON.stringify-ed unnecessarily, and at most once.
    
  641.     let nextPostbackStateKey = null;
    
  642. 
    
  643.     // Determine the current form state. If we received state during an MPA form
    
  644.     // submission, then we will reuse that, if the action identity matches.
    
  645.     // Otherwise we'll use the initial state argument. We will emit a comment
    
  646.     // marker into the stream that indicates whether the state was reused.
    
  647.     let state = initialState;
    
  648.     const componentKeyPath = (currentlyRenderingKeyPath: any);
    
  649.     const postbackFormState = getFormState(request);
    
  650.     // $FlowIgnore[prop-missing]
    
  651.     const isSignatureEqual = action.$$IS_SIGNATURE_EQUAL;
    
  652.     if (postbackFormState !== null && typeof isSignatureEqual === 'function') {
    
  653.       const postbackKey = postbackFormState[1];
    
  654.       const postbackReferenceId = postbackFormState[2];
    
  655.       const postbackBoundArity = postbackFormState[3];
    
  656.       if (
    
  657.         isSignatureEqual.call(action, postbackReferenceId, postbackBoundArity)
    
  658.       ) {
    
  659.         nextPostbackStateKey = createPostbackFormStateKey(
    
  660.           permalink,
    
  661.           componentKeyPath,
    
  662.           formStateHookIndex,
    
  663.         );
    
  664.         if (postbackKey === nextPostbackStateKey) {
    
  665.           // This was a match
    
  666.           formStateMatchingIndex = formStateHookIndex;
    
  667.           // Reuse the state that was submitted by the form.
    
  668.           state = postbackFormState[0];
    
  669.         }
    
  670.       }
    
  671.     }
    
  672. 
    
  673.     // Bind the state to the first argument of the action.
    
  674.     const boundAction = action.bind(null, state);
    
  675. 
    
  676.     // Wrap the action so the return value is void.
    
  677.     const dispatch = (payload: P): void => {
    
  678.       boundAction(payload);
    
  679.     };
    
  680. 
    
  681.     // $FlowIgnore[prop-missing]
    
  682.     if (typeof boundAction.$$FORM_ACTION === 'function') {
    
  683.       // $FlowIgnore[prop-missing]
    
  684.       dispatch.$$FORM_ACTION = (prefix: string) => {
    
  685.         const metadata: ReactCustomFormAction =
    
  686.           boundAction.$$FORM_ACTION(prefix);
    
  687. 
    
  688.         // Override the action URL
    
  689.         if (permalink !== undefined) {
    
  690.           if (__DEV__) {
    
  691.             checkAttributeStringCoercion(permalink, 'target');
    
  692.           }
    
  693.           permalink += '';
    
  694.           metadata.action = permalink;
    
  695.         }
    
  696. 
    
  697.         const formData = metadata.data;
    
  698.         if (formData) {
    
  699.           if (nextPostbackStateKey === null) {
    
  700.             nextPostbackStateKey = createPostbackFormStateKey(
    
  701.               permalink,
    
  702.               componentKeyPath,
    
  703.               formStateHookIndex,
    
  704.             );
    
  705.           }
    
  706.           formData.append('$ACTION_KEY', nextPostbackStateKey);
    
  707.         }
    
  708.         return metadata;
    
  709.       };
    
  710.     }
    
  711. 
    
  712.     return [state, dispatch];
    
  713.   } else {
    
  714.     // This is not a server action, so the implementation is much simpler.
    
  715. 
    
  716.     // Bind the state to the first argument of the action.
    
  717.     const boundAction = action.bind(null, initialState);
    
  718.     // Wrap the action so the return value is void.
    
  719.     const dispatch = (payload: P): void => {
    
  720.       boundAction(payload);
    
  721.     };
    
  722.     return [initialState, dispatch];
    
  723.   }
    
  724. }
    
  725. 
    
  726. function useId(): string {
    
  727.   const task: Task = (currentlyRenderingTask: any);
    
  728.   const treeId = getTreeId(task.treeContext);
    
  729. 
    
  730.   const resumableState = currentResumableState;
    
  731.   if (resumableState === null) {
    
  732.     throw new Error(
    
  733.       'Invalid hook call. Hooks can only be called inside of the body of a function component.',
    
  734.     );
    
  735.   }
    
  736. 
    
  737.   const localId = localIdCounter++;
    
  738.   return makeId(resumableState, treeId, localId);
    
  739. }
    
  740. 
    
  741. function use<T>(usable: Usable<T>): T {
    
  742.   if (usable !== null && typeof usable === 'object') {
    
  743.     // $FlowFixMe[method-unbinding]
    
  744.     if (typeof usable.then === 'function') {
    
  745.       // This is a thenable.
    
  746.       const thenable: Thenable<T> = (usable: any);
    
  747.       return unwrapThenable(thenable);
    
  748.     } else if (
    
  749.       usable.$$typeof === REACT_CONTEXT_TYPE ||
    
  750.       usable.$$typeof === REACT_SERVER_CONTEXT_TYPE
    
  751.     ) {
    
  752.       const context: ReactContext<T> = (usable: any);
    
  753.       return readContext(context);
    
  754.     }
    
  755.   }
    
  756. 
    
  757.   // eslint-disable-next-line react-internal/safe-string-coercion
    
  758.   throw new Error('An unsupported type was passed to use(): ' + String(usable));
    
  759. }
    
  760. 
    
  761. export function unwrapThenable<T>(thenable: Thenable<T>): T {
    
  762.   const index = thenableIndexCounter;
    
  763.   thenableIndexCounter += 1;
    
  764.   if (thenableState === null) {
    
  765.     thenableState = createThenableState();
    
  766.   }
    
  767.   return trackUsedThenable(thenableState, thenable, index);
    
  768. }
    
  769. 
    
  770. function unsupportedRefresh() {
    
  771.   throw new Error('Cache cannot be refreshed during server rendering.');
    
  772. }
    
  773. 
    
  774. function useCacheRefresh(): <T>(?() => T, ?T) => void {
    
  775.   return unsupportedRefresh;
    
  776. }
    
  777. 
    
  778. function useMemoCache(size: number): Array<any> {
    
  779.   const data = new Array<any>(size);
    
  780.   for (let i = 0; i < size; i++) {
    
  781.     data[i] = REACT_MEMO_CACHE_SENTINEL;
    
  782.   }
    
  783.   return data;
    
  784. }
    
  785. 
    
  786. function noop(): void {}
    
  787. 
    
  788. export const HooksDispatcher: Dispatcher = {
    
  789.   readContext,
    
  790.   use,
    
  791.   useContext,
    
  792.   useMemo,
    
  793.   useReducer,
    
  794.   useRef,
    
  795.   useState,
    
  796.   useInsertionEffect: noop,
    
  797.   useLayoutEffect: noop,
    
  798.   useCallback,
    
  799.   // useImperativeHandle is not run in the server environment
    
  800.   useImperativeHandle: noop,
    
  801.   // Effects are not run in the server environment.
    
  802.   useEffect: noop,
    
  803.   // Debugging effect
    
  804.   useDebugValue: noop,
    
  805.   useDeferredValue,
    
  806.   useTransition,
    
  807.   useId,
    
  808.   // Subscriptions are not setup in a server environment.
    
  809.   useSyncExternalStore,
    
  810. };
    
  811. 
    
  812. if (enableCache) {
    
  813.   HooksDispatcher.useCacheRefresh = useCacheRefresh;
    
  814. }
    
  815. if (enableUseEffectEventHook) {
    
  816.   HooksDispatcher.useEffectEvent = useEffectEvent;
    
  817. }
    
  818. if (enableUseMemoCacheHook) {
    
  819.   HooksDispatcher.useMemoCache = useMemoCache;
    
  820. }
    
  821. if (enableFormActions && enableAsyncActions) {
    
  822.   HooksDispatcher.useHostTransitionStatus = useHostTransitionStatus;
    
  823. }
    
  824. if (enableAsyncActions) {
    
  825.   HooksDispatcher.useOptimistic = useOptimistic;
    
  826.   HooksDispatcher.useFormState = useFormState;
    
  827. }
    
  828. 
    
  829. export let currentResumableState: null | ResumableState = (null: any);
    
  830. export function setCurrentResumableState(
    
  831.   resumableState: null | ResumableState,
    
  832. ): void {
    
  833.   currentResumableState = resumableState;
    
  834. }