@@ -13,8 +13,10 @@ let ReactNoop;
13
13
let Scheduler ;
14
14
let act ;
15
15
let assertLog ;
16
+ let useMemo ;
16
17
let useState ;
17
18
let useMemoCache ;
19
+ let waitForThrow ;
18
20
let MemoCacheSentinel ;
19
21
let ErrorBoundary ;
20
22
@@ -27,8 +29,10 @@ describe('useMemoCache()', () => {
27
29
Scheduler = require ( 'scheduler' ) ;
28
30
act = require ( 'internal-test-utils' ) . act ;
29
31
assertLog = require ( 'internal-test-utils' ) . assertLog ;
32
+ useMemo = React . useMemo ;
30
33
useMemoCache = require ( 'react/compiler-runtime' ) . c ;
31
34
useState = React . useState ;
35
+ waitForThrow = require ( 'internal-test-utils' ) . waitForThrow ;
32
36
MemoCacheSentinel = Symbol . for ( 'react.memo_cache_sentinel' ) ;
33
37
34
38
class _ErrorBoundary extends React . Component {
@@ -621,4 +625,59 @@ describe('useMemoCache()', () => {
621
625
</ > ,
622
626
) ;
623
627
} ) ;
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
+ } ) ;
624
683
} ) ;
0 commit comments