Skip to content

Commit 9612bb4

Browse files
committed
Fix useMemoCache with setState in render
ghstack-source-id: b3e3b82 Pull Request resolved: #30889
1 parent a06cd9e commit 9612bb4

File tree

2 files changed

+27
-13
lines changed

2 files changed

+27
-13
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,9 @@ function renderWithHooksAgain<Props, SecondArg>(
828828
currentHook = null;
829829
workInProgressHook = null;
830830

831-
workInProgress.updateQueue = null;
831+
if (workInProgress.updateQueue != null) {
832+
clearFunctionComponentUpdateQueue(workInProgress.updateQueue);
833+
}
832834

833835
if (__DEV__) {
834836
// Also validate hook order for cascading updates.
@@ -1101,6 +1103,20 @@ if (enableUseMemoCacheHook) {
11011103
};
11021104
}
11031105

1106+
// NOTE: this function intentionally does not reset memoCache. We reuse updateQueue for the memo
1107+
// cache to avoid increasing the size of fibers that don't need a cache, but we don't want to reset
1108+
// the cache when other properties are reset.
1109+
const clearFunctionComponentUpdateQueue = (
1110+
updateQueue: FunctionComponentUpdateQueue,
1111+
) => {
1112+
updateQueue.lastEffect = null;
1113+
updateQueue.events = null;
1114+
updateQueue.stores = null;
1115+
if (updateQueue.memoCache != null) {
1116+
updateQueue.memoCache.index = 0;
1117+
}
1118+
};
1119+
11041120
function useThenable<T>(thenable: Thenable<T>): T {
11051121
// Track the position of the thenable within this fiber.
11061122
const index = thenableIndexCounter;

packages/react-reconciler/src/__tests__/useMemoCache-test.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ describe('useMemoCache()', () => {
667667
}
668668

669669
// Baseline / source code
670-
function useUserMemo(value) {
670+
function useManualMemo(value) {
671671
return useMemo(() => [value], [value]);
672672
}
673673

@@ -683,24 +683,22 @@ describe('useMemoCache()', () => {
683683
}
684684

685685
/**
686-
* Test case: note that the initial render never completes
686+
* Test with useMemoCache
687687
*/
688688
let root = ReactNoop.createRoot();
689-
const IncorrectInfiniteComponent = makeComponent(useCompilerMemo);
690-
root.render(<IncorrectInfiniteComponent value={2} />);
691-
await waitForThrow(
692-
'Too many re-renders. React limits the number of renders to prevent ' +
693-
'an infinite loop.',
694-
);
689+
const CompilerMemoComponent = makeComponent(useCompilerMemo);
690+
await act(() => {
691+
root.render(<CompilerMemoComponent value={2} />);
692+
});
693+
expect(root).toMatchRenderedOutput(<div>2</div>);
695694

696695
/**
697-
* Baseline test: initial render is expected to complete after a retry
698-
* (triggered by the setState)
696+
* Test with useMemo
699697
*/
700698
root = ReactNoop.createRoot();
701-
const CorrectComponent = makeComponent(useUserMemo);
699+
const HookMemoComponent = makeComponent(useManualMemo);
702700
await act(() => {
703-
root.render(<CorrectComponent value={2} />);
701+
root.render(<HookMemoComponent value={2} />);
704702
});
705703
expect(root).toMatchRenderedOutput(<div>2</div>);
706704
});

0 commit comments

Comments
 (0)