@samitouri / QOS-React / commits / 25b99efe0c

[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;