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 * as React from 'react';
    
  11. import is from 'shared/objectIs';
    
  12. import {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStore';
    
  13. 
    
  14. // Intentionally not using named imports because Rollup uses dynamic dispatch
    
  15. // for CommonJS interop.
    
  16. const {useRef, useEffect, useMemo, useDebugValue} = React;
    
  17. 
    
  18. // Same as useSyncExternalStore, but supports selector and isEqual arguments.
    
  19. export function useSyncExternalStoreWithSelector<Snapshot, Selection>(
    
  20.   subscribe: (() => void) => () => void,
    
  21.   getSnapshot: () => Snapshot,
    
  22.   getServerSnapshot: void | null | (() => Snapshot),
    
  23.   selector: (snapshot: Snapshot) => Selection,
    
  24.   isEqual?: (a: Selection, b: Selection) => boolean,
    
  25. ): Selection {
    
  26.   // Use this to track the rendered snapshot.
    
  27.   const instRef = useRef<
    
  28.     | {
    
  29.         hasValue: true,
    
  30.         value: Selection,
    
  31.       }
    
  32.     | {
    
  33.         hasValue: false,
    
  34.         value: null,
    
  35.       }
    
  36.     | null,
    
  37.   >(null);
    
  38.   let inst;
    
  39.   if (instRef.current === null) {
    
  40.     inst = {
    
  41.       hasValue: false,
    
  42.       value: null,
    
  43.     };
    
  44.     instRef.current = inst;
    
  45.   } else {
    
  46.     inst = instRef.current;
    
  47.   }
    
  48. 
    
  49.   const [getSelection, getServerSelection] = useMemo(() => {
    
  50.     // Track the memoized state using closure variables that are local to this
    
  51.     // memoized instance of a getSnapshot function. Intentionally not using a
    
  52.     // useRef hook, because that state would be shared across all concurrent
    
  53.     // copies of the hook/component.
    
  54.     let hasMemo = false;
    
  55.     let memoizedSnapshot;
    
  56.     let memoizedSelection: Selection;
    
  57.     const memoizedSelector = (nextSnapshot: Snapshot) => {
    
  58.       if (!hasMemo) {
    
  59.         // The first time the hook is called, there is no memoized result.
    
  60.         hasMemo = true;
    
  61.         memoizedSnapshot = nextSnapshot;
    
  62.         const nextSelection = selector(nextSnapshot);
    
  63.         if (isEqual !== undefined) {
    
  64.           // Even if the selector has changed, the currently rendered selection
    
  65.           // may be equal to the new selection. We should attempt to reuse the
    
  66.           // current value if possible, to preserve downstream memoizations.
    
  67.           if (inst.hasValue) {
    
  68.             const currentSelection = inst.value;
    
  69.             if (isEqual(currentSelection, nextSelection)) {
    
  70.               memoizedSelection = currentSelection;
    
  71.               return currentSelection;
    
  72.             }
    
  73.           }
    
  74.         }
    
  75.         memoizedSelection = nextSelection;
    
  76.         return nextSelection;
    
  77.       }
    
  78. 
    
  79.       // We may be able to reuse the previous invocation's result.
    
  80.       const prevSnapshot: Snapshot = (memoizedSnapshot: any);
    
  81.       const prevSelection: Selection = (memoizedSelection: any);
    
  82. 
    
  83.       if (is(prevSnapshot, nextSnapshot)) {
    
  84.         // The snapshot is the same as last time. Reuse the previous selection.
    
  85.         return prevSelection;
    
  86.       }
    
  87. 
    
  88.       // The snapshot has changed, so we need to compute a new selection.
    
  89.       const nextSelection = selector(nextSnapshot);
    
  90. 
    
  91.       // If a custom isEqual function is provided, use that to check if the data
    
  92.       // has changed. If it hasn't, return the previous selection. That signals
    
  93.       // to React that the selections are conceptually equal, and we can bail
    
  94.       // out of rendering.
    
  95.       if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
    
  96.         return prevSelection;
    
  97.       }
    
  98. 
    
  99.       memoizedSnapshot = nextSnapshot;
    
  100.       memoizedSelection = nextSelection;
    
  101.       return nextSelection;
    
  102.     };
    
  103.     // Assigning this to a constant so that Flow knows it can't change.
    
  104.     const maybeGetServerSnapshot =
    
  105.       getServerSnapshot === undefined ? null : getServerSnapshot;
    
  106.     const getSnapshotWithSelector = () => memoizedSelector(getSnapshot());
    
  107.     const getServerSnapshotWithSelector =
    
  108.       maybeGetServerSnapshot === null
    
  109.         ? undefined
    
  110.         : () => memoizedSelector(maybeGetServerSnapshot());
    
  111.     return [getSnapshotWithSelector, getServerSnapshotWithSelector];
    
  112.   }, [getSnapshot, getServerSnapshot, selector, isEqual]);
    
  113. 
    
  114.   const value = useSyncExternalStore(
    
  115.     subscribe,
    
  116.     getSelection,
    
  117.     getServerSelection,
    
  118.   );
    
  119. 
    
  120.   useEffect(() => {
    
  121.     // $FlowFixMe[incompatible-type] changing the variant using mutation isn't supported
    
  122.     inst.hasValue = true;
    
  123.     // $FlowFixMe[incompatible-type]
    
  124.     inst.value = value;
    
  125.   }, [value]);
    
  126. 
    
  127.   useDebugValue(value);
    
  128.   return value;
    
  129. }