[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.
lauren committed
Apr 25, 2023 at 09:19 UTC
25b99efe0c9c9d593c86829386c86740d409fa8c
3 files changed
+77
-1
packages/react-debug-tools/src/ReactDebugHooks.js
+47
@@ -51,9 +51,19 @@ type Dispatch<A> = A => void;
51
52
let primitiveStackCache: null | Map<string, Array<any>> = null;
53
54
+type MemoCache = {
55
+ data: Array<Array<any>>,
56
+ index: number,
57
+};
58
+
59
+type FunctionComponentUpdateQueue = {
60
+ memoCache?: MemoCache | null,
61
+};
62
+
63
type Hook = {
64
memoizedState: any,
65
next: Hook | null,
66
+ updateQueue: FunctionComponentUpdateQueue | null,
67
};
68
69
function getPrimitiveStackCache(): Map<string, Array<any>> {
@@ -79,6 +89,10 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
89
Dispatcher.useDebugValue(null);
90
Dispatcher.useCallback(() => {});
91
Dispatcher.useMemo(() => null);
92
+ if (typeof Dispatcher.useMemoCache === 'function') {
93
+ // This type check is for Flow only.
94
+ Dispatcher.useMemoCache(0);
95
+ }
96
} finally {
97
readHookLog = hookLog;
98
hookLog = [];
@@ -333,6 +347,38 @@ function useId(): string {
347
return id;
348
}
349
350
+function useMemoCache(size: number): Array<any> {
351
+ const hook = nextHook();
352
+ let memoCache: MemoCache;
353
+ if (
354
+ hook !== null &&
355
+ hook.updateQueue !== null &&
356
+ hook.updateQueue.memoCache != null
357
+ ) {
358
+ memoCache = hook.updateQueue.memoCache;
359
+ } else {
360
+ memoCache = {
361
+ data: [],
362
+ index: 0,
363
+ };
364
+ }
365
+
366
+ let data = memoCache.data[memoCache.index];
367
+ if (data === undefined) {
368
+ const MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel');
369
+ data = new Array(size);
370
+ for (let i = 0; i < size; i++) {
371
+ data[i] = MEMO_CACHE_SENTINEL;
372
+ }
373
+ }
374
+ hookLog.push({
375
+ primitive: 'MemoCache',
376
+ stackError: new Error(),
377
+ value: data,
378
+ });
379
+ return data;
380
+}
381
+
382
const Dispatcher: DispatcherType = {
383
use,
384
readContext,
@@ -345,6 +391,7 @@ const Dispatcher: DispatcherType = {
391
useLayoutEffect,
392
useInsertionEffect,
393
useMemo,
394
+ useMemoCache,
395
useReducer,
396
useRef,
397
useState,
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+29
@@ -14,6 +14,7 @@ let React;
14
let ReactTestRenderer;
15
let ReactDebugTools;
16
let act;
17
+let useMemoCache;
18
19
describe('ReactHooksInspectionIntegration', () => {
20
beforeEach(() => {
@@ -22,6 +23,7 @@ describe('ReactHooksInspectionIntegration', () => {
23
ReactTestRenderer = require('react-test-renderer');
24
act = require('internal-test-utils').act;
25
ReactDebugTools = require('react-debug-tools');
26
+ useMemoCache = React.unstable_useMemoCache;
27
});
28
29
it('should inspect the current state of useState hooks', async () => {
@@ -633,6 +635,33 @@ describe('ReactHooksInspectionIntegration', () => {
635
});
636
});
637
638
+ // @gate enableUseMemoCacheHook
639
+ it('should support useMemoCache hook', () => {
640
+ function Foo() {
641
+ const $ = useMemoCache(1);
642
+ let t0;
643
+
644
+ if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
645
+ t0 = <div>{1}</div>;
646
+ $[0] = t0;
647
+ } else {
648
+ t0 = $[0];
649
+ }
650
+
651
+ return t0;
652
+ }
653
+
654
+ const renderer = ReactTestRenderer.create(<Foo />);
655
+ const childFiber = renderer.root.findByType(Foo)._currentFiber();
656
+ const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
657
+
658
+ expect(tree.length).toEqual(1);
659
+ expect(tree[0].isStateEditable).toBe(false);
660
+ expect(tree[0].name).toBe('MemoCache');
661
+ expect(tree[0].value).toHaveLength(1);
662
+ expect(tree[0].value[0]).toEqual(<div>{1}</div>);
663
+ });
664
+
665
describe('useDebugValue', () => {
666
it('should support inspectable values for multiple custom hooks', () => {
667
function useLabeledValue(label) {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
-1
@@ -39,7 +39,7 @@ export const disableModulePatternComponents = false;
39
export const enableSuspenseAvoidThisFallback = false;
40
export const enableSuspenseAvoidThisFallbackFizz = false;
41
export const enableCPUSuspense = false;
42
-export const enableUseMemoCacheHook = false;
42
+export const enableUseMemoCacheHook = true;
43
export const enableUseEffectEventHook = false;
44
export const enableClientRenderFallbackOnTextMismatch = true;
45
export const enableComponentStackLocations = true;