Skip to content

Commit aba370f

Browse files
authored
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.
1 parent 1345c37 commit aba370f

8 files changed

+29
-2
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import {
9393
enableTrustedTypesIntegration,
9494
enableAsyncActions,
9595
disableLegacyMode,
96+
enableMoveBefore,
9697
} from 'shared/ReactFeatureFlags';
9798
import {
9899
HostComponent,
@@ -525,6 +526,7 @@ export function appendInitialChild(
525526
parentInstance: Instance,
526527
child: Instance | TextInstance,
527528
): void {
529+
// Note: This should not use moveBefore() because initial are appended while disconnected.
528530
parentInstance.appendChild(child);
529531
}
530532

@@ -757,11 +759,22 @@ export function commitTextUpdate(
757759
textInstance.nodeValue = newText;
758760
}
759761

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+
760768
export function appendChild(
761769
parentInstance: Instance,
762770
child: Instance | TextInstance,
763771
): 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+
}
765778
}
766779

767780
export function appendChildToContainer(
@@ -799,7 +812,12 @@ export function insertBefore(
799812
child: Instance | TextInstance,
800813
beforeChild: Instance | TextInstance | SuspenseInstance,
801814
): 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+
}
803821
}
804822

805823
export function insertInContainerBefore(

packages/shared/ReactFeatureFlags.js

+3
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ export const disableIEWorkarounds = true;
210210
// request for certain browsers.
211211
export const enableFilterEmptyStringAttributesDOM = true;
212212

213+
// Enable the moveBefore() alternative to insertBefore(). This preserves states of moves.
214+
export const enableMoveBefore = false;
215+
213216
// Disabled caching behavior of `react/cache` in client runtimes.
214217
export const disableClientCache = true;
215218

packages/shared/forks/ReactFeatureFlags.native-fb.js

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const enableDebugTracing = false;
5555
export const enableDeferRootSchedulingToMicrotask = true;
5656
export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
5757
export const enableFilterEmptyStringAttributesDOM = true;
58+
export const enableMoveBefore = true;
5859
export const enableFizzExternalRuntime = true;
5960
export const enableFlightReadableStream = true;
6061
export const enableGetInspectorDataForInstanceInProduction = true;

packages/shared/forks/ReactFeatureFlags.native-oss.js

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
4444
export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
4545
export const enableFabricCompleteRootInCommitPhase = false;
4646
export const enableFilterEmptyStringAttributesDOM = true;
47+
export const enableMoveBefore = true;
4748
export const enableFizzExternalRuntime = true;
4849
export const enableFlightReadableStream = true;
4950
export const enableGetInspectorDataForInstanceInProduction = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const favorSafetyOverHydrationPerf = true;
4545
export const enableComponentStackLocations = true;
4646
export const enableLegacyFBSupport = false;
4747
export const enableFilterEmptyStringAttributesDOM = true;
48+
export const enableMoveBefore = false;
4849
export const enableGetInspectorDataForInstanceInProduction = false;
4950
export const enableFabricCompleteRootInCommitPhase = false;
5051
export const enableHiddenSubtreeInsertionEffectCleanup = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const enableDebugTracing = false;
3535
export const enableDeferRootSchedulingToMicrotask = true;
3636
export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
3737
export const enableFilterEmptyStringAttributesDOM = true;
38+
export const enableMoveBefore = false;
3839
export const enableFizzExternalRuntime = true;
3940
export const enableFlightReadableStream = true;
4041
export const enableGetInspectorDataForInstanceInProduction = false;

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const favorSafetyOverHydrationPerf = true;
4747
export const enableComponentStackLocations = true;
4848
export const enableLegacyFBSupport = false;
4949
export const enableFilterEmptyStringAttributesDOM = true;
50+
export const enableMoveBefore = false;
5051
export const enableGetInspectorDataForInstanceInProduction = false;
5152
export const enableRenderableContext = false;
5253
export const enableFabricCompleteRootInCommitPhase = false;

packages/shared/forks/ReactFeatureFlags.www.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const enableCPUSuspense = true;
5757
export const enableUseMemoCacheHook = true;
5858
export const enableUseEffectEventHook = true;
5959
export const enableFilterEmptyStringAttributesDOM = true;
60+
export const enableMoveBefore = false;
6061
export const enableAsyncActions = true;
6162
export const disableInputAttributeSyncing = false;
6263
export const enableLegacyFBSupport = true;

0 commit comments

Comments
 (0)