From 631400de7e1ba2ce351f11bab2179d4eba480dd1 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Fri, 21 Apr 2023 15:48:41 -0400 Subject: [PATCH 1/5] [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. --- .../react-debug-tools/src/ReactDebugHooks.js | 48 +++++++++++++++++++ .../ReactHooksInspectionIntegration-test.js | 36 ++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 362e5f36d2bb1..4829722558d08 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -30,6 +30,8 @@ import { ForwardRef, } from 'react-reconciler/src/ReactWorkTags'; +const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel'); + type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher; // Used to track hooks called during a render @@ -51,9 +53,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 +91,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 +349,37 @@ 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) { + 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 +392,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..e2e90b4428b81 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -633,6 +633,42 @@ describe('ReactHooksInspectionIntegration', () => { }); }); + // @gate enableUseMemoCacheHook + it('should support useMemoCache hook', () => { + function Foo() { + const $ = React.unstable_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]).toMatchInlineSnapshot(` + { + "id": 0, + "isStateEditable": false, + "name": "MemoCache", + "subHooks": [], + "value": [ +
+ 1 +
, + ], + } + `); + }); + describe('useDebugValue', () => { it('should support inspectable values for multiple custom hooks', () => { function useLabeledValue(label) { From 72fe051eea0a999513e959cfc7042bc1117c16c2 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Mon, 24 Apr 2023 13:12:17 -0400 Subject: [PATCH 2/5] Try turning on enableUseMemoCacheHook in test renderer --- packages/shared/forks/ReactFeatureFlags.test-renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From b9beb67dd50c93c21ccc20434031bec3b823373b Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Mon, 24 Apr 2023 13:38:34 -0400 Subject: [PATCH 3/5] Don't use inline snapshot --- .../ReactHooksInspectionIntegration-test.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index e2e90b4428b81..a8aa7c5cf938a 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -654,19 +654,10 @@ describe('ReactHooksInspectionIntegration', () => { const tree = ReactDebugTools.inspectHooksOfFiber(childFiber); expect(tree.length).toEqual(1); - expect(tree[0]).toMatchInlineSnapshot(` - { - "id": 0, - "isStateEditable": false, - "name": "MemoCache", - "subHooks": [], - "value": [ -
- 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', () => { From 3124fcfbec5e24ef4dc9bd9a26ec68974c079e41 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 25 Apr 2023 11:52:13 -0400 Subject: [PATCH 4/5] Alias unstable_useMemoCache to useMemoCache --- .../src/__tests__/ReactHooksInspectionIntegration-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index a8aa7c5cf938a..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 () => { @@ -636,7 +638,7 @@ describe('ReactHooksInspectionIntegration', () => { // @gate enableUseMemoCacheHook it('should support useMemoCache hook', () => { function Foo() { - const $ = React.unstable_useMemoCache(1); + const $ = useMemoCache(1); let t0; if ($[0] === Symbol.for('react.memo_cache_sentinel')) { From de0445d359e026540905304142da35aec0fa8466 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 25 Apr 2023 12:05:29 -0400 Subject: [PATCH 5/5] Move sentinel var into function body We don't need this symbol allocated unless we're handling a useMemoCache call --- packages/react-debug-tools/src/ReactDebugHooks.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 4829722558d08..42fc7fbe16288 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -30,8 +30,6 @@ import { ForwardRef, } from 'react-reconciler/src/ReactWorkTags'; -const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel'); - type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher; // Used to track hooks called during a render @@ -367,6 +365,7 @@ function useMemoCache(size: number): Array { 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;