@@ -13,8 +13,10 @@ let ReactNoop;
1313let Scheduler ;
1414let act ;
1515let assertLog ;
16+ let useMemo ;
1617let useState ;
1718let useMemoCache ;
19+ let waitForThrow ;
1820let MemoCacheSentinel ;
1921let ErrorBoundary ;
2022
@@ -27,8 +29,10 @@ describe('useMemoCache()', () => {
2729 Scheduler = require ( 'scheduler' ) ;
2830 act = require ( 'internal-test-utils' ) . act ;
2931 assertLog = require ( 'internal-test-utils' ) . assertLog ;
32+ useMemo = React . useMemo ;
3033 useMemoCache = require ( 'react/compiler-runtime' ) . c ;
3134 useState = React . useState ;
35+ waitForThrow = require ( 'internal-test-utils' ) . waitForThrow ;
3236 MemoCacheSentinel = Symbol . for ( 'react.memo_cache_sentinel' ) ;
3337
3438 class _ErrorBoundary extends React . Component {
@@ -621,4 +625,59 @@ describe('useMemoCache()', () => {
621625 </ > ,
622626 ) ;
623627 } ) ;
628+
629+ // @gate enableUseMemoCacheHook
630+ it ( '(repro) infinite renders when used with setState during render' , async ( ) => {
631+ // Output of react compiler on `useUserMemo`
632+ function useCompilerMemo ( value ) {
633+ let arr ;
634+ const $ = useMemoCache ( 2 ) ;
635+ if ( $ [ 0 ] !== value ) {
636+ arr = [ value ] ;
637+ $ [ 0 ] = value ;
638+ $ [ 1 ] = arr ;
639+ } else {
640+ arr = $ [ 1 ] ;
641+ }
642+ return arr ;
643+ }
644+
645+ // Baseline / source code
646+ function useUserMemo ( value ) {
647+ return useMemo ( ( ) => [ value ] , [ value ] ) ;
648+ }
649+
650+ function makeComponent ( hook ) {
651+ return function Component ( { value} ) {
652+ const state = hook ( value ) ;
653+ const [ prevState , setPrevState ] = useState ( null ) ;
654+ if ( state !== prevState ) {
655+ setPrevState ( state ) ;
656+ }
657+ return < div > { state . join ( ',' ) } </ div > ;
658+ } ;
659+ }
660+
661+ /**
662+ * Test case: note that the initial render never completes
663+ */
664+ let root = ReactNoop . createRoot ( ) ;
665+ const IncorrectInfiniteComponent = makeComponent ( useCompilerMemo ) ;
666+ root . render ( < IncorrectInfiniteComponent value = { 2 } /> ) ;
667+ await waitForThrow (
668+ 'Too many re-renders. React limits the number of renders to prevent ' +
669+ 'an infinite loop.' ,
670+ ) ;
671+
672+ /**
673+ * Baseline test: initial render is expected to complete after a retry
674+ * (triggered by the setState)
675+ */
676+ root = ReactNoop . createRoot ( ) ;
677+ const CorrectComponent = makeComponent ( useUserMemo ) ;
678+ await act ( ( ) => {
679+ root . render ( < CorrectComponent value = { 2 } /> ) ;
680+ } ) ;
681+ expect ( root ) . toMatchRenderedOutput ( < div > 2</ div > ) ;
682+ } ) ;
624683} ) ;
0 commit comments