Repository: facebook/react
Pull request: [DevTools] Add support for useMemoCache

useMemoCache wasn't previously supported in the DevTools, so any attempt to inspect a component using the hook would result in a `dispatcher.useMemoCache is not a function (it is undefined)` error.


Changed files:
filename: packages/react-debug-tools/src/ReactDebugHooks.js
patch: @@ -51,9 +51,19 @@ type Dispatch<A> = A => void;
 
 let primitiveStackCache: null | Map<string, Array<any>> = null;
 
+type MemoCache = {
+  data: Array<Array<any>>,
+  index: number,
+};
+
+type FunctionComponentUpdateQueue = {
+  memoCache?: MemoCache | null,
+};
+
 type Hook = {
   memoizedState: any,
   next: Hook | null,
+  updateQueue: FunctionComponentUpdateQueue | null,
 };
 
 function getPrimitiveStackCache(): Map<string, Array<any>> {
@@ -79,6 +89,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
       Dispatcher.useDebugValue(null);
       Dispatcher.useCallback(() => {});
       Dispatcher.useMemo(() => null);
+      if (typeof Dispatcher.useMemoCache === 'function') {
+        // This type check is for Flow only.
+        Dispatcher.useMemoCache(0);
+      }
     } finally {
       readHookLog = hookLog;
       hookLog = [];
@@ -333,6 +347,38 @@ function useId(): string {
   return id;
 }
 
+function useMemoCache(size: number): Array<any> {
+  const hook = nextHook();
+  let memoCache: MemoCache;
+  if (
+    hook !== null &&
+    hook.updateQueue !== null &&
+    hook.updateQueue.memoCache != null
+  ) {
+    memoCache = hook.updateQueue.memoCache;
+  } else {
+    memoCache = {
+      data: [],
+      index: 0,
+    };
+  }
+
+  let data = memoCache.data[memoCache.index];
+  if (data === undefined) {
+    const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel');
+    data = new Array(size);
+    for (let i = 0; i < size; i++) {
+      data[i] = MEMO_CACHE_SENTINEL;
+    }
+  }
+  hookLog.push({
+    primitive: 'MemoCache',
+    stackError: new Error(),
+    value: data,
+  });
+  return data;
+}
+
 const Dispatcher: DispatcherType = {
   use,
   readContext,
@@ -345,6 +391,7 @@ const Dispatcher: DispatcherType = {
   useLayoutEffect,
   useInsertionEffect,
   useMemo,
+  useMemoCache,
   useReducer,
   useRef,
   useState,

filename: packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
patch: @@ -14,6 +14,7 @@ let React;
 let ReactTestRenderer;
 let ReactDebugTools;
 let act;
+let useMemoCache;
 
 describe('ReactHooksInspectionIntegration', () => {
   beforeEach(() => {
@@ -22,6 +23,7 @@ describe('ReactHooksInspectionIntegration', () => {
     ReactTestRenderer = require('react-test-renderer');
     act = require('internal-test-utils').act;
     ReactDebugTools = require('react-debug-tools');
+    useMemoCache = React.unstable_useMemoCache;
   });
 
   it('should inspect the current state of useState hooks', async () => {
@@ -633,6 +635,33 @@ describe('ReactHooksInspectionIntegration', () => {
     });
   });
 
+  // @gate enableUseMemoCacheHook
+  it('should support useMemoCache hook', () => {
+    function Foo() {
+      const $ = useMemoCache(1);
+      let t0;
+
+      if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
+        t0 = <div>{1}</div>;
+        $[0] = t0;
+      } else {
+        t0 = $[0];
+      }
+
+      return t0;
+    }
+
+    const renderer = ReactTestRenderer.create(<Foo />);
+    const childFiber = renderer.root.findByType(Foo)._currentFiber();
+    const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
+
+    expect(tree.length).toEqual(1);
+    expect(tree[0].isStateEditable).toBe(false);
+    expect(tree[0].name).toBe('MemoCache');
+    expect(tree[0].value).toHaveLength(1);
+    expect(tree[0].value[0]).toEqual(<div>{1}</div>);
+  });
+
   describe('useDebugValue', () => {
     it('should support inspectable values for multiple custom hooks', () => {
       function useLabeledValue(label) {

filename: packages/shared/forks/ReactFeatureFlags.test-renderer.js
patch: @@ -39,7 +39,7 @@ export const disableModulePatternComponents = false;
 export const enableSuspenseAvoidThisFallback = false;
 export const enableSuspenseAvoidThisFallbackFizz = false;
 export const enableCPUSuspense = false;
-export const enableUseMemoCacheHook = false;
+export const enableUseMemoCacheHook = true;
 export const enableUseEffectEventHook = false;
 export const enableClientRenderFallbackOnTextMismatch = true;
 export const enableComponentStackLocations = true;