From 8626a1b5813edb3fad3b1881e825aedee05e6dea Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 13 Aug 2024 14:54:02 -0400 Subject: [PATCH 1/2] Update [ghstack-poisoned] --- packages/react-reconciler/src/ReactFiber.js | 4 ++ .../react-reconciler/src/ReactFiberHooks.js | 10 ++- .../src/ReactFiberHotReloading.js | 4 ++ .../src/ReactInternalTypes.js | 1 + .../__tests__/ReactFreshIntegration-test.js | 64 ++++++++++++++++++- 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index db9c4773444e1..55d10f2224872 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -210,6 +210,7 @@ function FiberNode( this._debugTask = null; } this._debugNeedsRemount = false; + this._debugNeedsMemoCacheReset = false; this._debugHookTypes = null; if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') { Object.preventExtensions(this); @@ -301,6 +302,7 @@ function createFiberImplObject( fiber._debugTask = null; } fiber._debugNeedsRemount = false; + fiber._debugNeedsMemoCacheReset = false; fiber._debugHookTypes = null; if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') { Object.preventExtensions(fiber); @@ -423,6 +425,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { if (__DEV__) { workInProgress._debugInfo = current._debugInfo; workInProgress._debugNeedsRemount = current._debugNeedsRemount; + workInProgress._debugNeedsMemoCacheReset = + current._debugNeedsMemoCacheReset; switch (workInProgress.tag) { case FunctionComponent: case SimpleMemoComponent: diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 15b3c38037809..1e283195eb3a6 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -1232,7 +1232,15 @@ function useMemoCache(size: number): Array { // values from being reused. In prod environments this is never expected to happen. However, in // the unlikely case that it does vary between renders, we reset the cache anyway so behavior is // consistent in both environments. - if (data === undefined || data.length !== size) { + // + // The cache is also reset if the fiber is being rerendered as a result of Fast Refresh cycle, + // even if the cache size incidentally happens to be the same. This is to ensure that we don't + // see incorrect values after a refresh. + if ( + data === undefined || + data.length !== size || + currentlyRenderingFiber._debugNeedsMemoCacheReset === true + ) { data = memoCache.data[memoCache.index] = new Array(size); for (let i = 0; i < size; i++) { data[i] = REACT_MEMO_CACHE_SENTINEL; diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.js b/packages/react-reconciler/src/ReactFiberHotReloading.js index c42bb0b2c6d12..643cfa549f816 100644 --- a/packages/react-reconciler/src/ReactFiberHotReloading.js +++ b/packages/react-reconciler/src/ReactFiberHotReloading.js @@ -307,6 +307,10 @@ function scheduleFibersWithFamiliesRecursively( } } + if (fiber.updateQueue != null && fiber.updateQueue.memoCache != null) { + fiber._debugNeedsMemoCacheReset = true; + } + if (needsRemount) { fiber._debugNeedsRemount = true; } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 4549253ba79b6..4e8e37a7c6489 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -210,6 +210,7 @@ export type Fiber = { _debugTask?: ConsoleTask | null, _debugIsCurrentlyTiming?: boolean, _debugNeedsRemount?: boolean, + _debugNeedsMemoCacheReset?: boolean, // Used to verify that the order of hooks does not change between renders. _debugHookTypes?: Array | null, diff --git a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js index cc9deb6236011..4e80aae3e071d 100644 --- a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js +++ b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js @@ -1637,7 +1637,69 @@ describe('ReactFreshIntegration', () => { } }); - it('resets useMemoCache cache slots', async () => { + it('resets useMemoCache cache slots in a refresh', async () => { + if (__DEV__) { + await render(` + const useMemoCache = require('react/compiler-runtime').c; + let cacheMisses = 0; + const cacheMiss = (id) => { + cacheMisses++; + return id; + }; + export default function App(t0) { + const $ = useMemoCache(2); + const {reset1, reset2} = t0; + let t1; + if ($[0] !== reset1) { + $[0] = t1 = cacheMiss({reset1}); + } else { + t1 = $[1]; + } + let t2; + if ($[1] !== reset2) { + $[1] = t2 = cacheMiss({reset2}); + } else { + t2 = $[1]; + } + return

{cacheMisses}

; + } + `); + const el = container.firstChild; + expect(el.textContent).toBe('2'); + await patch(` + const useMemoCache = require('react/compiler-runtime').c; + let cacheMisses = 0; + const cacheMiss = (id) => { + cacheMisses++; + return id; + }; + export default function App(t0) { + const $ = useMemoCache(2); + const {foo, bar} = t0; + let t1; + if ($[0] !== foo) { + $[0] = t1 = cacheMiss({foo}); + } else { + t1 = $[1]; + } + let t2; + if ($[1] !== bar) { + $[1] = t2 = cacheMiss({bar}); + } else { + t2 = $[1]; + } + return

{cacheMisses}

; + } + `); + expect(container.firstChild).toBe(el); + // cache size is constant but the cache is cleared anyway as the component in question was + // fast refreshed. this can occur in the case where the cache size incidentally happens to + // stay constant even though the source code was changed. + expect(el.textContent).toBe('2'); + } + }); + + it('resets useMemoCache cache slots when cache size changes', async () => { if (__DEV__) { await render(` const useMemoCache = require('react/compiler-runtime').c; From 69c32b47e94c1b862d3944074f4da6f61c0eceb1 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Tue, 13 Aug 2024 15:02:37 -0400 Subject: [PATCH 2/2] Update [ghstack-poisoned] --- packages/react-reconciler/src/ReactFiberHooks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 1e283195eb3a6..c6c3154ad36bd 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -1233,9 +1233,9 @@ function useMemoCache(size: number): Array { // the unlikely case that it does vary between renders, we reset the cache anyway so behavior is // consistent in both environments. // - // The cache is also reset if the fiber is being rerendered as a result of Fast Refresh cycle, - // even if the cache size incidentally happens to be the same. This is to ensure that we don't - // see incorrect values after a refresh. + // The cache is also reset if the fiber is being rerendered as a result of Fast Refresh run, even + // if the cache size incidentally happens to be the same. This is to ensure that we don't see + // incorrect values after a refresh. if ( data === undefined || data.length !== size ||