Skip to content

Commit 7226818

Browse files
committed
Make sure Offscreen's ref is detached when unmounted
1 parent a8f7a96 commit 7226818

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.new.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ function commitLayoutEffectOnFiber(
11211121
if (flags & Ref) {
11221122
safelyAttachRef(finishedWork, finishedWork.return);
11231123
}
1124-
} else if (finishedWork.pendingProps.mode !== undefined) {
1124+
} else {
11251125
safelyDetachRef(finishedWork, finishedWork.return);
11261126
}
11271127
} else {
@@ -2148,6 +2148,8 @@ function commitDeletionEffectsOnFiber(
21482148
offscreenSubtreeWasHidden =
21492149
prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
21502150

2151+
safelyDetachRef(deletedFiber, nearestMountedAncestor);
2152+
21512153
recursivelyTraverseDeletionEffects(
21522154
finishedRoot,
21532155
nearestMountedAncestor,
@@ -2833,6 +2835,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
28332835
// Nested Offscreen tree is already hidden. Don't disappear
28342836
// its effects.
28352837
} else {
2838+
safelyDetachRef(finishedWork, finishedWork.return);
28362839
recursivelyTraverseDisappearLayoutEffects(finishedWork);
28372840
}
28382841
break;
@@ -2974,6 +2977,8 @@ export function reappearLayoutEffects(
29742977
finishedWork,
29752978
includeWorkInProgressEffects,
29762979
);
2980+
2981+
safelyAttachRef(finishedWork, finishedWork.return);
29772982
}
29782983
break;
29792984
}

packages/react-reconciler/src/ReactFiberCommitWork.old.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ function commitLayoutEffectOnFiber(
11211121
if (flags & Ref) {
11221122
safelyAttachRef(finishedWork, finishedWork.return);
11231123
}
1124-
} else if (finishedWork.pendingProps.mode !== undefined) {
1124+
} else {
11251125
safelyDetachRef(finishedWork, finishedWork.return);
11261126
}
11271127
} else {
@@ -2148,6 +2148,8 @@ function commitDeletionEffectsOnFiber(
21482148
offscreenSubtreeWasHidden =
21492149
prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
21502150

2151+
safelyDetachRef(deletedFiber, nearestMountedAncestor);
2152+
21512153
recursivelyTraverseDeletionEffects(
21522154
finishedRoot,
21532155
nearestMountedAncestor,
@@ -2833,6 +2835,7 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
28332835
// Nested Offscreen tree is already hidden. Don't disappear
28342836
// its effects.
28352837
} else {
2838+
safelyDetachRef(finishedWork, finishedWork.return);
28362839
recursivelyTraverseDisappearLayoutEffects(finishedWork);
28372840
}
28382841
break;
@@ -2974,6 +2977,8 @@ export function reappearLayoutEffects(
29742977
finishedWork,
29752978
includeWorkInProgressEffects,
29762979
);
2980+
2981+
safelyAttachRef(finishedWork, finishedWork.return);
29772982
}
29782983
break;
29792984
}

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,4 +1307,86 @@ describe('ReactOffscreen', () => {
13071307
expect(offscreenRef.current).not.toBeNull();
13081308
});
13091309
});
1310+
1311+
// @gate enableOffscreen
1312+
it('should detach ref if Offscreen is unmounted', async () => {
1313+
let offscreenRef;
1314+
1315+
function App({showOffscreen}) {
1316+
offscreenRef = useRef(null);
1317+
return showOffscreen ? (
1318+
<Offscreen
1319+
mode={'manual'}
1320+
ref={ref => {
1321+
offscreenRef.current = ref;
1322+
}}>
1323+
<div />
1324+
</Offscreen>
1325+
) : null;
1326+
}
1327+
1328+
const root = ReactNoop.createRoot();
1329+
1330+
await act(async () => {
1331+
root.render(<App showOffscreen={true} />);
1332+
});
1333+
1334+
expect(offscreenRef.current).not.toBeNull();
1335+
1336+
await act(async () => {
1337+
root.render(<App showOffscreen={false} />);
1338+
});
1339+
1340+
expect(offscreenRef.current).toBeNull();
1341+
1342+
await act(async () => {
1343+
root.render(<App showOffscreen={true} />);
1344+
});
1345+
1346+
expect(offscreenRef.current).not.toBeNull();
1347+
});
1348+
1349+
// @gate enableOffscreen
1350+
it('should detach ref if Offscreen is unmounted', async () => {
1351+
let offscreenRef;
1352+
let divRef;
1353+
1354+
function App({mode}) {
1355+
offscreenRef = useRef(null);
1356+
divRef = useRef(null);
1357+
return (
1358+
<Offscreen mode={mode}>
1359+
<Offscreen
1360+
mode={'manual'}
1361+
ref={offscreenRef}>
1362+
<div ref={divRef} />
1363+
</Offscreen>
1364+
</Offscreen>
1365+
);
1366+
}
1367+
1368+
const root = ReactNoop.createRoot();
1369+
1370+
await act(async () => {
1371+
root.render(<App mode={'hidden'} />);
1372+
});
1373+
1374+
expect(offscreenRef.current).toBeNull();
1375+
expect(divRef.current).toBeNull();
1376+
1377+
await act(async () => {
1378+
root.render(<App mode={'visible'} />);
1379+
});
1380+
1381+
expect(offscreenRef.current).not.toBeNull();
1382+
expect(divRef.current).not.toBeNull();
1383+
1384+
console.log('Becoming Hidden');
1385+
await act(async () => {
1386+
root.render(<App mode={'hidden'} />);
1387+
});
1388+
1389+
expect(offscreenRef.current).toBeNull();
1390+
expect(divRef.current).toBeNull();
1391+
});
13101392
});

0 commit comments

Comments
 (0)