From 6a1722d1f04af18849e053e6af6bc82f1cdf03c7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Thu, 29 Aug 2024 16:59:36 -0400 Subject: [PATCH] [compiler][runtime] repro: infinite render with useMemoCache + render phase updates --- .../src/__tests__/useMemoCache-test.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/react-reconciler/src/__tests__/useMemoCache-test.js b/packages/react-reconciler/src/__tests__/useMemoCache-test.js index a3dbeeb38ce1c..9ea0ca7fc3afd 100644 --- a/packages/react-reconciler/src/__tests__/useMemoCache-test.js +++ b/packages/react-reconciler/src/__tests__/useMemoCache-test.js @@ -13,8 +13,10 @@ let ReactNoop; let Scheduler; let act; let assertLog; +let useMemo; let useState; let useMemoCache; +let waitForThrow; let MemoCacheSentinel; let ErrorBoundary; @@ -27,8 +29,10 @@ describe('useMemoCache()', () => { Scheduler = require('scheduler'); act = require('internal-test-utils').act; assertLog = require('internal-test-utils').assertLog; + useMemo = React.useMemo; useMemoCache = require('react/compiler-runtime').c; useState = React.useState; + waitForThrow = require('internal-test-utils').waitForThrow; MemoCacheSentinel = Symbol.for('react.memo_cache_sentinel'); class _ErrorBoundary extends React.Component { @@ -621,4 +625,59 @@ describe('useMemoCache()', () => { , ); }); + + // @gate enableUseMemoCacheHook + it('(repro) infinite renders when used with setState during render', async () => { + // Output of react compiler on `useUserMemo` + function useCompilerMemo(value) { + let arr; + const $ = useMemoCache(2); + if ($[0] !== value) { + arr = [value]; + $[0] = value; + $[1] = arr; + } else { + arr = $[1]; + } + return arr; + } + + // Baseline / source code + function useUserMemo(value) { + return useMemo(() => [value], [value]); + } + + function makeComponent(hook) { + return function Component({value}) { + const state = hook(value); + const [prevState, setPrevState] = useState(null); + if (state !== prevState) { + setPrevState(state); + } + return
{state.join(',')}
; + }; + } + + /** + * Test case: note that the initial render never completes + */ + let root = ReactNoop.createRoot(); + const IncorrectInfiniteComponent = makeComponent(useCompilerMemo); + root.render(); + await waitForThrow( + 'Too many re-renders. React limits the number of renders to prevent ' + + 'an infinite loop.', + ); + + /** + * Baseline test: initial render is expected to complete after a retry + * (triggered by the setState) + */ + root = ReactNoop.createRoot(); + const CorrectComponent = makeComponent(useUserMemo); + await act(() => { + root.render(); + }); + expect(root).toMatchRenderedOutput(
2
); + }); });