Skip to content

Commit aec521a

Browse files
authored
fix[devtools/useMemoCache]: implement a working copy of useMemoCache (#27659)
In #27472 I've removed broken `useMemoCache` implementation and replaced it with a stub. It actually produces errors when trying to inspect components, which are compiled with Forget. The main difference from the implementation in #26696 is that we are using corresponding `Fiber` here, which has patched `updateQueue` with `memoCache`. Previously we would check it on a hook object, which doesn't have `updateQueue`. Tested on pages, which are using Forget and by inspecting elements, which are transpiled with Forget.
1 parent 593ecee commit aec521a

File tree

2 files changed

+97
-24
lines changed

2 files changed

+97
-24
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ContextProvider,
2727
ForwardRef,
2828
} from 'react-reconciler/src/ReactWorkTags';
29+
import {REACT_MEMO_CACHE_SENTINEL} from 'shared/ReactSymbols';
2930

3031
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
3132

@@ -93,7 +94,9 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
9394
return primitiveStackCache;
9495
}
9596

97+
let currentFiber: null | Fiber = null;
9698
let currentHook: null | Hook = null;
99+
97100
function nextHook(): null | Hook {
98101
const hook = currentHook;
99102
if (hook !== null) {
@@ -319,9 +322,31 @@ function useId(): string {
319322

320323
// useMemoCache is an implementation detail of Forget's memoization
321324
// it should not be called directly in user-generated code
322-
// we keep it as a stub for dispatcher
323325
function useMemoCache(size: number): Array<any> {
324-
return [];
326+
const fiber = currentFiber;
327+
// Don't throw, in case this is called from getPrimitiveStackCache
328+
if (fiber == null) {
329+
return [];
330+
}
331+
332+
// $FlowFixMe[incompatible-use]: updateQueue is mixed
333+
const memoCache = fiber.updateQueue?.memoCache;
334+
if (memoCache == null) {
335+
return [];
336+
}
337+
338+
let data = memoCache.data[memoCache.index];
339+
if (data === undefined) {
340+
data = memoCache.data[memoCache.index] = new Array(size);
341+
for (let i = 0; i < size; i++) {
342+
data[i] = REACT_MEMO_CACHE_SENTINEL;
343+
}
344+
}
345+
346+
// We don't write anything to hookLog on purpose, so this hook remains invisible to users.
347+
348+
memoCache.index++;
349+
return data;
325350
}
326351

327352
const Dispatcher: DispatcherType = {
@@ -699,9 +724,11 @@ export function inspectHooks<Props>(
699724
}
700725

701726
const previousDispatcher = currentDispatcher.current;
702-
let readHookLog;
703727
currentDispatcher.current = DispatcherProxy;
728+
729+
let readHookLog;
704730
let ancestorStackError;
731+
705732
try {
706733
ancestorStackError = new Error();
707734
renderFunction(props);
@@ -798,19 +825,25 @@ export function inspectHooksOfFiber(
798825
'Unknown Fiber. Needs to be a function component to inspect hooks.',
799826
);
800827
}
828+
801829
// Warm up the cache so that it doesn't consume the currentHook.
802830
getPrimitiveStackCache();
831+
832+
// Set up the current hook so that we can step through and read the
833+
// current state from them.
834+
currentHook = (fiber.memoizedState: Hook);
835+
currentFiber = fiber;
836+
803837
const type = fiber.type;
804838
let props = fiber.memoizedProps;
805839
if (type !== fiber.elementType) {
806840
props = resolveDefaultProps(type, props);
807841
}
808-
// Set up the current hook so that we can step through and read the
809-
// current state from them.
810-
currentHook = (fiber.memoizedState: Hook);
811-
const contextMap = new Map<ReactContext<$FlowFixMe>, $FlowFixMe>();
842+
843+
const contextMap = new Map<ReactContext<any>, any>();
812844
try {
813845
setupContexts(contextMap, fiber);
846+
814847
if (fiber.tag === ForwardRef) {
815848
return inspectHooksOfForwardRef(
816849
type.render,
@@ -820,9 +853,12 @@ export function inspectHooksOfFiber(
820853
includeHooksSource,
821854
);
822855
}
856+
823857
return inspectHooks(type, props, currentDispatcher, includeHooksSource);
824858
} finally {
859+
currentFiber = null;
825860
currentHook = null;
861+
826862
restoreContexts(contextMap);
827863
}
828864
}

packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -633,27 +633,64 @@ describe('ReactHooksInspectionIntegration', () => {
633633
});
634634
});
635635

636-
// @gate enableUseMemoCacheHook
637-
it('useMemoCache should not be inspectable', () => {
638-
function Foo() {
639-
const $ = useMemoCache(1);
640-
let t0;
641-
642-
if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
643-
t0 = <div>{1}</div>;
644-
$[0] = t0;
645-
} else {
646-
t0 = $[0];
636+
describe('useMemoCache', () => {
637+
// @gate enableUseMemoCacheHook
638+
it('should not be inspectable', () => {
639+
function Foo() {
640+
const $ = useMemoCache(1);
641+
let t0;
642+
643+
if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
644+
t0 = <div>{1}</div>;
645+
$[0] = t0;
646+
} else {
647+
t0 = $[0];
648+
}
649+
650+
return t0;
647651
}
648652

649-
return t0;
650-
}
653+
const renderer = ReactTestRenderer.create(<Foo />);
654+
const childFiber = renderer.root.findByType(Foo)._currentFiber();
655+
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
651656

652-
const renderer = ReactTestRenderer.create(<Foo />);
653-
const childFiber = renderer.root.findByType(Foo)._currentFiber();
654-
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
657+
expect(tree.length).toEqual(0);
658+
});
659+
660+
// @gate enableUseMemoCacheHook
661+
it('should work in combination with other hooks', () => {
662+
function useSomething() {
663+
const [something] = React.useState(null);
664+
const changeOtherSomething = React.useCallback(() => {}, [something]);
665+
666+
return [something, changeOtherSomething];
667+
}
655668

656-
expect(tree.length).toEqual(0);
669+
function Foo() {
670+
const $ = useMemoCache(10);
671+
672+
useSomething();
673+
React.useState(1);
674+
React.useEffect(() => {});
675+
676+
let t0;
677+
678+
if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
679+
t0 = <div>{1}</div>;
680+
$[0] = t0;
681+
} else {
682+
t0 = $[0];
683+
}
684+
685+
return t0;
686+
}
687+
688+
const renderer = ReactTestRenderer.create(<Foo />);
689+
const childFiber = renderer.root.findByType(Foo)._currentFiber();
690+
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
691+
692+
expect(tree.length).toEqual(3);
693+
});
657694
});
658695

659696
describe('useDebugValue', () => {

0 commit comments

Comments
 (0)