diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 5c36e6407032b..9834ac842c767 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -428,6 +428,11 @@ export function renderWithHooks( do { didScheduleRenderPhaseUpdate = false; numberOfReRenders += 1; + if (__DEV__) { + // Even when hot reloading, allow dependencies to stabilize + // after first render to prevent infinite render phase updates. + ignorePreviousDependencies = false; + } // Start over from the beginning of the list nextCurrentHook = current !== null ? current.memoizedState : null; diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index c2de5062a5948..240fcfa5980a4 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -2350,6 +2350,49 @@ describe('ReactFresh', () => { } }); + // This pattern is inspired by useSubscription and similar mechanisms. + it('does not get into infinite loops during render phase updates', () => { + if (__DEV__) { + render(() => { + function Hello() { + const source = React.useMemo(() => ({value: 10}), []); + const [state, setState] = React.useState({value: null}); + if (state !== source) { + setState(source); + } + return
{state.value}
; + } + $RefreshReg$(Hello, 'Hello'); + return Hello; + }); + + const el = container.firstChild; + expect(el.textContent).toBe('10'); + expect(el.style.color).toBe('blue'); + + // Perform a hot update. + act(() => { + patch(() => { + function Hello() { + const source = React.useMemo(() => ({value: 20}), []); + const [state, setState] = React.useState({value: null}); + if (state !== source) { + // This should perform a single render-phase update. + setState(source); + } + return{state.value}
; + } + $RefreshReg$(Hello, 'Hello'); + return Hello; + }); + }); + + expect(container.firstChild).toBe(el); + expect(el.textContent).toBe('20'); + expect(el.style.color).toBe('red'); + } + }); + it('can hot reload offscreen components', () => { if (__DEV__) { const AppV1 = prepare(() => {