Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fresh] Reset useMemoCache in response to a FastRefresh run #30677

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
10 changes: 9 additions & 1 deletion packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,15 @@ function useMemoCache(size: number): Array<any> {
// 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 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 ||
currentlyRenderingFiber._debugNeedsMemoCacheReset === true
) {
data = memoCache.data[memoCache.index] = new Array(size);
for (let i = 0; i < size; i++) {
data[i] = REACT_MEMO_CACHE_SENTINEL;
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberHotReloading.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ function scheduleFibersWithFamiliesRecursively(
}
}

if (fiber.updateQueue != null && fiber.updateQueue.memoCache != null) {
fiber._debugNeedsMemoCacheReset = true;
}

if (needsRemount) {
fiber._debugNeedsRemount = true;
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactInternalTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<HookType> | null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <h1>{cacheMisses}</h1>;
}
`);
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 <h1>{cacheMisses}</h1>;
}
`);
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;
Expand Down
Loading