Skip to content

Commit da9325b

Browse files
authored
[Fiber] Double invoke Effects in StrictMode after Fast Refresh (#35962)
1 parent 67e4759 commit da9325b

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3866,7 +3866,7 @@ function remountFiber(
38663866
deletions.push(current);
38673867
}
38683868

3869-
newWorkInProgress.flags |= Placement;
3869+
newWorkInProgress.flags |= Placement | PlacementDEV;
38703870

38713871
// Restart work from the new fiber.
38723872
return newWorkInProgress;

packages/react-refresh/src/__tests__/ReactFresh-test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,6 +2326,98 @@ describe('ReactFresh', () => {
23262326
expect(finalEl.style.color).toBe('orange');
23272327
}
23282328

2329+
it('double invokes effects after a forced remount in StrictMode', async () => {
2330+
if (__DEV__) {
2331+
const log = [];
2332+
2333+
const createAppV1 = () => {
2334+
function Hello() {
2335+
React.useEffect(() => {
2336+
log.push('mount v1');
2337+
return () => log.push('unmount v1');
2338+
}, []);
2339+
return <p style={{color: 'blue'}}>Hello</p>;
2340+
}
2341+
$RefreshReg$(Hello, 'Hello');
2342+
$RefreshSig$(Hello, '1');
2343+
2344+
return Hello;
2345+
};
2346+
2347+
const App = createAppV1();
2348+
2349+
await act(() => {
2350+
root.render(
2351+
<React.StrictMode>
2352+
<App />
2353+
</React.StrictMode>,
2354+
);
2355+
});
2356+
2357+
expect(log).toEqual(['mount v1', 'unmount v1', 'mount v1']);
2358+
log.length = 0;
2359+
2360+
await patch(() => {
2361+
function Hello() {
2362+
React.useEffect(() => {
2363+
log.push('mount v2');
2364+
return () => log.push('unmount v2');
2365+
}, []);
2366+
return <p style={{color: 'red'}}>Hello</p>;
2367+
}
2368+
$RefreshReg$(Hello, 'Hello');
2369+
$RefreshSig$(Hello, '2');
2370+
return null;
2371+
});
2372+
2373+
expect(container.firstChild.style.color).toBe('red');
2374+
expect(log).toEqual(['unmount v1', 'mount v2', 'unmount v2', 'mount v2']);
2375+
}
2376+
});
2377+
2378+
it('double invokes an effect added during Fast Refresh remount in StrictMode', async () => {
2379+
if (__DEV__) {
2380+
const log = [];
2381+
2382+
const createAppV1 = () => {
2383+
function Hello() {
2384+
return <p style={{color: 'blue'}}>Hello</p>;
2385+
}
2386+
$RefreshReg$(Hello, 'Hello');
2387+
$RefreshSig$(Hello, '1');
2388+
return Hello;
2389+
};
2390+
2391+
const App = createAppV1();
2392+
2393+
await act(() => {
2394+
root.render(
2395+
<React.StrictMode>
2396+
<App />
2397+
</React.StrictMode>,
2398+
);
2399+
});
2400+
2401+
expect(log).toEqual([]);
2402+
2403+
await patch(() => {
2404+
function Hello() {
2405+
React.useEffect(() => {
2406+
log.push('mount v2');
2407+
return () => log.push('unmount v2');
2408+
}, []);
2409+
return <p style={{color: 'red'}}>Hello</p>;
2410+
}
2411+
$RefreshReg$(Hello, 'Hello');
2412+
$RefreshSig$(Hello, '2');
2413+
return null;
2414+
});
2415+
2416+
expect(container.firstChild.style.color).toBe('red');
2417+
expect(log).toEqual(['mount v2', 'unmount v2', 'mount v2']);
2418+
}
2419+
});
2420+
23292421
it('resets hooks with dependencies on hot reload', async () => {
23302422
if (__DEV__) {
23312423
let useEffectWithEmptyArrayCalls = 0;

0 commit comments

Comments
 (0)