@samitouri / QOS-React-2 / commits / aec521a96d

fix[devtools/useMemoCache]: implement a working copy of useMemoCache (#27659)

In https://github.com/facebook/react/pull/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 https://github.com/facebook/react/pull/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.

Ruslan Lesiutin committed Nov 14, 2023 at 18:23 UTC aec521a96d3f1bebc2ba38553d14f4989c6e88e0
2 files changed +97 -24
packages/react-debug-tools/src/ReactDebugHooks.js
+43 -7
@@ -26,6 +26,7 @@ import {
26 ContextProvider,
27 ForwardRef,
28 } from 'react-reconciler/src/ReactWorkTags';
29 +import {REACT_MEMO_CACHE_SENTINEL} from 'shared/ReactSymbols';
30
31 type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
32
@@ -93,7 +94,9 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
94 return primitiveStackCache;
95 }
96
97 +let currentFiber: null | Fiber = null;
98 let currentHook: null | Hook = null;
99 +
100 function nextHook(): null | Hook {
101 const hook = currentHook;
102 if (hook !== null) {
@@ -319,9 +322,31 @@ function useId(): string {
322
323 // useMemoCache is an implementation detail of Forget's memoization
324 // it should not be called directly in user-generated code
322 -// we keep it as a stub for dispatcher
325 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;
350 }
351
352 const Dispatcher: DispatcherType = {
@@ -699,9 +724,11 @@ export function inspectHooks<Props>(
724 }
725
726 const previousDispatcher = currentDispatcher.current;
702 - let readHookLog;
727 currentDispatcher.current = DispatcherProxy;
728 +
729 + let readHookLog;
730 let ancestorStackError;
731 +
732 try {
733 ancestorStackError = new Error();
734 renderFunction(props);
@@ -798,19 +825,25 @@ export function inspectHooksOfFiber(
825 'Unknown Fiber. Needs to be a function component to inspect hooks.',
826 );
827 }
828 +
829 // Warm up the cache so that it doesn't consume the currentHook.
830 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 +
837 const type = fiber.type;
838 let props = fiber.memoizedProps;
839 if (type !== fiber.elementType) {
840 props = resolveDefaultProps(type, props);
841 }
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>();
844 try {
845 setupContexts(contextMap, fiber);
846 +
847 if (fiber.tag === ForwardRef) {
848 return inspectHooksOfForwardRef(
849 type.render,
@@ -820,9 +853,12 @@ export function inspectHooksOfFiber(
853 includeHooksSource,
854 );
855 }
856 +
857 return inspectHooks(type, props, currentDispatcher, includeHooksSource);
858 } finally {
859 + currentFiber = null;
860 currentHook = null;
861 +
862 restoreContexts(contextMap);
863 }
864 }
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+54 -17
@@ -633,27 +633,64 @@ describe('ReactHooksInspectionIntegration', () => {
633 });
634 });
635
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;
651 }
652
649 - return t0;
650 - }
653 + const renderer = ReactTestRenderer.create(<Foo />);
654 + const childFiber = renderer.root.findByType(Foo)._currentFiber();
655 + const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
656
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 + }
668
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 + });
694 });
695
696 describe('useDebugValue', () => {