From e233b5475581285f0923e6edc93dbe2350ba55f7 Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 25 Apr 2023 09:19:25 -0700 Subject: [PATCH] [DevTools] Add support for useMemoCache (#26696) 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. --- .../react-debug-tools/src/ReactDebugHooks.js | 47 +++++++++++++++++++ .../ReactHooksInspectionIntegration-test.js | 29 ++++++++++++ .../forks/ReactFeatureFlags.test-renderer.js | 2 +- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 362e5f36d2bb1..42fc7fbe16288 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -51,9 +51,19 @@ type Dispatch = A => void; let primitiveStackCache: null | Map> = null; +type MemoCache = { + data: Array>, + index: number, +}; + +type FunctionComponentUpdateQueue = { + memoCache?: MemoCache | null, +}; + type Hook = { memoizedState: any, next: Hook | null, + updateQueue: FunctionComponentUpdateQueue | null, }; function getPrimitiveStackCache(): Map> { @@ -79,6 +89,10 @@ function getPrimitiveStackCache(): Map> { 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 { + 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, diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 5e4860ce7045b..7b27b57f63ad9 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -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 =
{1}
; + $[0] = t0; + } else { + t0 = $[0]; + } + + return t0; + } + + const renderer = ReactTestRenderer.create(); + 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(
{1}
); + }); + describe('useDebugValue', () => { it('should support inspectable values for multiple custom hooks', () => { function useLabeledValue(label) { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index f3d296f257c92..bbc988448c543 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -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;