Prevent stale legacy root from clearing a container (DRAFT) (#18792)
* Don't clear a container because of a stale legacy root * Added test repro for FB error
Brian Vaughn committed
Apr 30, 2020 at 15:56 UTC
ac533fde3dff81023809a92d3f5432df7fc2c418
3 files changed
+62
-4
packages/react-dom/src/__tests__/ReactDOMFiber-test.js
+32
@@ -1280,4 +1280,36 @@ describe('ReactDOMFiber', () => {
1280
);
1281
expect(didCallOnChange).toBe(true);
1282
});
1283
+
1284
+ it('unmounted legacy roots should never clear newer root content from a container', () => {
1285
+ const ref = React.createRef();
1286
+
1287
+ function OldApp() {
1288
+ const hideOnFocus = () => {
1289
+ // This app unmounts itself inside of a focus event.
1290
+ ReactDOM.unmountComponentAtNode(container);
1291
+ };
1292
+
1293
+ return (
1294
+ <button onFocus={hideOnFocus} ref={ref}>
1295
+ old
1296
+ </button>
1297
+ );
1298
+ }
1299
+
1300
+ function NewApp() {
1301
+ return <button ref={ref}>new</button>;
1302
+ }
1303
+
1304
+ ReactDOM.render(<OldApp />, container);
1305
+ ref.current.focus();
1306
+
1307
+ ReactDOM.render(<NewApp />, container);
1308
+
1309
+ // Calling focus again will flush previously scheduled discerete work for the old root-
1310
+ // but this should not clear out the newly mounted app.
1311
+ ref.current.focus();
1312
+
1313
+ expect(container.textContent).toBe('new');
1314
+ });
1315
});
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+15
-2
@@ -687,8 +687,21 @@ function completeWork(
687
// It's also safe to do for updates too, because current.child would only be null
688
// if the previous render was null (so the the container would already be empty).
689
//
690
- // The additional root.hydrate check is required for hydration in legacy mode with no fallback.
691
- workInProgress.effectTag |= Snapshot;
690
+ // The additional root.hydrate check above is required for hydration in legacy mode with no fallback.
691
+ //
692
+ // The root container check below also avoids a potential legacy mode problem
693
+ // where unmounting from a container then rendering into it again
694
+ // can sometimes cause the container to be cleared after the new render.
695
+ const containerInfo = fiberRoot.containerInfo;
696
+ const legacyRootContainer =
697
+ containerInfo == null ? null : containerInfo._reactRootContainer;
698
+ if (
699
+ legacyRootContainer == null ||
700
+ legacyRootContainer._internalRoot == null ||
701
+ legacyRootContainer._internalRoot === fiberRoot
702
+ ) {
703
+ workInProgress.effectTag |= Snapshot;
704
+ }
705
}
706
}
707
updateHostContainer(workInProgress);
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+15
-2
@@ -685,8 +685,21 @@ function completeWork(
685
// It's also safe to do for updates too, because current.child would only be null
686
// if the previous render was null (so the the container would already be empty).
687
//
688
- // The additional root.hydrate check is required for hydration in legacy mode with no fallback.
689
- workInProgress.effectTag |= Snapshot;
688
+ // The additional root.hydrate check above is required for hydration in legacy mode with no fallback.
689
+ //
690
+ // The root container check below also avoids a potential legacy mode problem
691
+ // where unmounting from a container then rendering into it again
692
+ // can sometimes cause the container to be cleared after the new render.
693
+ const containerInfo = fiberRoot.containerInfo;
694
+ const legacyRootContainer =
695
+ containerInfo == null ? null : containerInfo._reactRootContainer;
696
+ if (
697
+ legacyRootContainer == null ||
698
+ legacyRootContainer._internalRoot == null ||
699
+ legacyRootContainer._internalRoot === fiberRoot
700
+ ) {
701
+ workInProgress.effectTag |= Snapshot;
702
+ }
703
}
704
}
705
updateHostContainer(workInProgress);