@samitouri / QOS-React-2 / commits / aba370f1e4

Add moveBefore Experiment (#31596)

A long standing issue for React has been that if you reorder stateful nodes, they may lose their state and reload. The thing moving loses its state. There's no way to solve this in general where two stateful nodes swap. The [`moveBefore()` proposal](https://chromestatus.com/feature/5135990159835136?gate=5177450351558656) has now moved to [intent-to-ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/YE_xLH6MkRs/m/_7CD0NYMAAAJ). This function is kind of like `insertBefore` but preserves state. There's [a demo here](https://state-preserving-atomic-move.glitch.me/). Ideally we'd port this demo to a fixture so we can try it. Currently this flag is always off - even in experimental. That's because this is still behind a Chrome flag so it's a little early to turn it on even in experimental. So you need a custom build. It's on in RN but only because it doesn't apply there which makes it easier to tell that it's safe to ship once it's on everywhere else. The other reason it's still off is because there's currently a semantic breaking change. `moveBefore()` errors if both nodes are disconnected. That happens if we're inside a completely disconnected React root. That's not usually how you should use React because it means effects can't read layout etc. However, it is currently supported. To handle this we'd have to try/catch the `moveBefore` to handle this case but we hope this semantic will change before it ships. Before we turn this on in experimental we either have to wait for the implementation to not error in the disconnected-disconnected case in Chrome or we'd have to add try/catch.

Sebastian Markbåge committed Nov 22, 2024 at 13:24 UTC aba370f1e45d21f19f33c04c33fc99fb3d0109e5
8 files changed +29 -2
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+20 -2
@@ -93,6 +93,7 @@ import {
93 enableTrustedTypesIntegration,
94 enableAsyncActions,
95 disableLegacyMode,
96 + enableMoveBefore,
97 } from 'shared/ReactFeatureFlags';
98 import {
99 HostComponent,
@@ -525,6 +526,7 @@ export function appendInitialChild(
526 parentInstance: Instance,
527 child: Instance | TextInstance,
528 ): void {
529 + // Note: This should not use moveBefore() because initial are appended while disconnected.
530 parentInstance.appendChild(child);
531 }
532
@@ -757,11 +759,22 @@ export function commitTextUpdate(
759 textInstance.nodeValue = newText;
760 }
761
762 +const supportsMoveBefore =
763 + // $FlowFixMe[prop-missing]: We're doing the feature detection here.
764 + enableMoveBefore &&
765 + typeof window !== 'undefined' &&
766 + typeof window.Node.prototype.moveBefore === 'function';
767 +
768 export function appendChild(
769 parentInstance: Instance,
770 child: Instance | TextInstance,
771 ): void {
764 - parentInstance.appendChild(child);
772 + if (supportsMoveBefore) {
773 + // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.
774 + parentInstance.moveBefore(child, null);
775 + } else {
776 + parentInstance.appendChild(child);
777 + }
778 }
779
780 export function appendChildToContainer(
@@ -799,7 +812,12 @@ export function insertBefore(
812 child: Instance | TextInstance,
813 beforeChild: Instance | TextInstance | SuspenseInstance,
814 ): void {
802 - parentInstance.insertBefore(child, beforeChild);
815 + if (supportsMoveBefore) {
816 + // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.
817 + parentInstance.moveBefore(child, beforeChild);
818 + } else {
819 + parentInstance.insertBefore(child, beforeChild);
820 + }
821 }
822
823 export function insertInContainerBefore(
packages/shared/ReactFeatureFlags.js
+3
@@ -210,6 +210,9 @@ export const disableIEWorkarounds = true;
210 // request for certain browsers.
211 export const enableFilterEmptyStringAttributesDOM = true;
212
213 +// Enable the moveBefore() alternative to insertBefore(). This preserves states of moves.
214 +export const enableMoveBefore = false;
215 +
216 // Disabled caching behavior of `react/cache` in client runtimes.
217 export const disableClientCache = true;
218
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -55,6 +55,7 @@ export const enableDebugTracing = false;
55 export const enableDeferRootSchedulingToMicrotask = true;
56 export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
57 export const enableFilterEmptyStringAttributesDOM = true;
58 +export const enableMoveBefore = true;
59 export const enableFizzExternalRuntime = true;
60 export const enableFlightReadableStream = true;
61 export const enableGetInspectorDataForInstanceInProduction = true;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -44,6 +44,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
44 export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
45 export const enableFabricCompleteRootInCommitPhase = false;
46 export const enableFilterEmptyStringAttributesDOM = true;
47 +export const enableMoveBefore = true;
48 export const enableFizzExternalRuntime = true;
49 export const enableFlightReadableStream = true;
50 export const enableGetInspectorDataForInstanceInProduction = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -45,6 +45,7 @@ export const favorSafetyOverHydrationPerf = true;
45 export const enableComponentStackLocations = true;
46 export const enableLegacyFBSupport = false;
47 export const enableFilterEmptyStringAttributesDOM = true;
48 +export const enableMoveBefore = false;
49 export const enableGetInspectorDataForInstanceInProduction = false;
50 export const enableFabricCompleteRootInCommitPhase = false;
51 export const enableHiddenSubtreeInsertionEffectCleanup = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -35,6 +35,7 @@ export const enableDebugTracing = false;
35 export const enableDeferRootSchedulingToMicrotask = true;
36 export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
37 export const enableFilterEmptyStringAttributesDOM = true;
38 +export const enableMoveBefore = false;
39 export const enableFizzExternalRuntime = true;
40 export const enableFlightReadableStream = true;
41 export const enableGetInspectorDataForInstanceInProduction = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -47,6 +47,7 @@ export const favorSafetyOverHydrationPerf = true;
47 export const enableComponentStackLocations = true;
48 export const enableLegacyFBSupport = false;
49 export const enableFilterEmptyStringAttributesDOM = true;
50 +export const enableMoveBefore = false;
51 export const enableGetInspectorDataForInstanceInProduction = false;
52 export const enableRenderableContext = false;
53 export const enableFabricCompleteRootInCommitPhase = false;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -57,6 +57,7 @@ export const enableCPUSuspense = true;
57 export const enableUseMemoCacheHook = true;
58 export const enableUseEffectEventHook = true;
59 export const enableFilterEmptyStringAttributesDOM = true;
60 +export const enableMoveBefore = false;
61 export const enableAsyncActions = true;
62 export const disableInputAttributeSyncing = false;
63 export const enableLegacyFBSupport = true;