diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index 9f954f4581d3c..b1a88868b3389 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -1905,10 +1905,19 @@ describe('ReactDOMServerPartialHydration', () => {
resolve();
await promise;
});
- expect(clicks).toBe(1);
-
- expect(container.textContent).toBe('Hello');
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(clicks).toBe(0);
+ expect(container.textContent).toBe('Click meHello');
+ } else {
+ expect(clicks).toBe(1);
+ expect(container.textContent).toBe('Hello');
+ }
document.body.removeChild(container);
});
@@ -1991,7 +2000,16 @@ describe('ReactDOMServerPartialHydration', () => {
await promise;
});
- expect(onEvent).toHaveBeenCalledTimes(2);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(onEvent).toHaveBeenCalledTimes(0);
+ } else {
+ expect(onEvent).toHaveBeenCalledTimes(2);
+ }
document.body.removeChild(container);
});
@@ -2072,7 +2090,17 @@ describe('ReactDOMServerPartialHydration', () => {
resolve();
await promise;
});
- expect(clicks).toBe(2);
+
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(clicks).toBe(0);
+ } else {
+ expect(clicks).toBe(2);
+ }
document.body.removeChild(container);
});
@@ -2158,7 +2186,16 @@ describe('ReactDOMServerPartialHydration', () => {
resolve();
await promise;
});
- expect(onEvent).toHaveBeenCalledTimes(2);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(onEvent).toHaveBeenCalledTimes(0);
+ } else {
+ expect(onEvent).toHaveBeenCalledTimes(2);
+ }
document.body.removeChild(container);
});
@@ -2231,9 +2268,19 @@ describe('ReactDOMServerPartialHydration', () => {
await promise;
});
- expect(clicksOnChild).toBe(1);
- // This will be zero due to the stopPropagation.
- expect(clicksOnParent).toBe(0);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(clicksOnChild).toBe(0);
+ expect(clicksOnParent).toBe(0);
+ } else {
+ expect(clicksOnChild).toBe(1);
+ // This will be zero due to the stopPropagation.
+ expect(clicksOnParent).toBe(0);
+ }
document.body.removeChild(container);
});
@@ -2310,8 +2357,16 @@ describe('ReactDOMServerPartialHydration', () => {
});
// We're now full hydrated.
-
- expect(clicks).toBe(1);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(clicks).toBe(0);
+ } else {
+ expect(clicks).toBe(1);
+ }
document.body.removeChild(parentContainer);
});
@@ -2580,8 +2635,20 @@ describe('ReactDOMServerPartialHydration', () => {
await promise;
});
- expect(submits).toBe(1);
- expect(container.textContent).toBe('Hello');
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ // discrete event not replayed
+ expect(submits).toBe(0);
+ expect(container.textContent).toBe('Click meHello');
+ } else {
+ expect(submits).toBe(1);
+ expect(container.textContent).toBe('Hello');
+ }
+
document.body.removeChild(container);
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
index 76d9b43d07f61..bb04b46df1c40 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
@@ -14,6 +14,7 @@ import {createEventTarget} from 'dom-event-testing-library';
let React;
let ReactDOM;
let ReactDOMServer;
+let ReactFeatureFlags;
let Scheduler;
let Suspense;
let act;
@@ -112,7 +113,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
beforeEach(() => {
jest.resetModuleRegistry();
- const ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableCreateEventHandleAPI = true;
React = require('react');
ReactDOM = require('react-dom');
@@ -266,9 +267,19 @@ describe('ReactDOMServerSelectiveHydration', () => {
resolve();
await promise;
});
- // After the click, we should prioritize D and the Click first,
- // and only after that render A and C.
- expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
+
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(Scheduler).toHaveYielded(['D', 'A']);
+ } else {
+ // After the click, we should prioritize D and the Click first,
+ // and only after that render A and C.
+ expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
+ }
document.body.removeChild(container);
});
@@ -343,7 +354,16 @@ describe('ReactDOMServerSelectiveHydration', () => {
dispatchClickEvent(spanC);
dispatchClickEvent(spanD);
- expect(Scheduler).toHaveYielded(['App']);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(Scheduler).toHaveYielded(['App', 'C', 'Clicked C']);
+ } else {
+ expect(Scheduler).toHaveYielded(['App']);
+ }
await act(async () => {
suspend = false;
@@ -351,18 +371,29 @@ describe('ReactDOMServerSelectiveHydration', () => {
await promise;
});
- // We should prioritize hydrating A, C and D first since we clicked in
- // them. Only after they're done will we hydrate B.
- expect(Scheduler).toHaveYielded([
- 'A',
- 'Clicked A',
- 'C',
- 'Clicked C',
- 'D',
- 'Clicked D',
- // B should render last since it wasn't clicked.
- 'B',
- ]);
+ if (
+ ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
+ ) {
+ expect(Scheduler).toHaveYielded([
+ 'A',
+ 'D',
+ // B should render last since it wasn't clicked.
+ 'B',
+ ]);
+ } else {
+ // We should prioritize hydrating A, C and D first since we clicked in
+ // them. Only after they're done will we hydrate B.
+ expect(Scheduler).toHaveYielded([
+ 'A',
+ 'Clicked A',
+ 'C',
+ 'Clicked C',
+ 'D',
+ 'Clicked D',
+ // B should render last since it wasn't clicked.
+ 'B',
+ ]);
+ }
document.body.removeChild(container);
});
@@ -519,7 +550,17 @@ describe('ReactDOMServerSelectiveHydration', () => {
resolve();
await promise;
});
- expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ // no replay
+ expect(Scheduler).toHaveYielded(['D', 'A']);
+ } else {
+ expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
+ }
document.body.removeChild(container);
});
@@ -599,26 +640,42 @@ describe('ReactDOMServerSelectiveHydration', () => {
createEventTarget(spanC).virtualclick();
createEventTarget(spanD).virtualclick();
- expect(Scheduler).toHaveYielded(['App']);
-
+ if (
+ ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
+ ) {
+ expect(Scheduler).toHaveYielded(['App', 'C', 'Clicked C']);
+ } else {
+ expect(Scheduler).toHaveYielded(['App']);
+ }
await act(async () => {
suspend = false;
resolve();
await promise;
});
- // We should prioritize hydrating A, C and D first since we clicked in
- // them. Only after they're done will we hydrate B.
- expect(Scheduler).toHaveYielded([
- 'A',
- 'Clicked A',
- 'C',
- 'Clicked C',
- 'D',
- 'Clicked D',
- // B should render last since it wasn't clicked.
- 'B',
- ]);
+ if (
+ ReactFeatureFlags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
+ ) {
+ expect(Scheduler).toHaveYielded([
+ 'A',
+ 'D',
+ // B should render last since it wasn't clicked.
+ 'B',
+ ]);
+ } else {
+ // We should prioritize hydrating A, C and D first since we clicked in
+ // them. Only after they're done will we hydrate B.
+ expect(Scheduler).toHaveYielded([
+ 'A',
+ 'Clicked A',
+ 'C',
+ 'Clicked C',
+ 'D',
+ 'Clicked D',
+ // B should render last since it wasn't clicked.
+ 'B',
+ ]);
+ }
document.body.removeChild(container);
});
@@ -707,20 +764,37 @@ describe('ReactDOMServerSelectiveHydration', () => {
await promise;
});
- // We should prioritize hydrating D first because we clicked it.
- // Next we should hydrate C since that's the current hover target.
- // To simplify implementation details we hydrate both B and C at
- // the same time since B was already scheduled.
- // This is ok because it will at least not continue for nested
- // boundary. See the next test below.
- expect(Scheduler).toHaveYielded([
- 'D',
- 'Clicked D',
- 'B', // Ideally this should be later.
- 'C',
- 'Hover C',
- 'A',
- ]);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ // We should prioritize hydrating D first because we clicked it.
+ // but event isnt replayed
+ expect(Scheduler).toHaveYielded([
+ 'D',
+ 'B', // Ideally this should be later.
+ 'C',
+ 'Hover C',
+ 'A',
+ ]);
+ } else {
+ // We should prioritize hydrating D first because we clicked it.
+ // Next we should hydrate C since that's the current hover target.
+ // To simplify implementation details we hydrate both B and C at
+ // the same time since B was already scheduled.
+ // This is ok because it will at least not continue for nested
+ // boundary. See the next test below.
+ expect(Scheduler).toHaveYielded([
+ 'D',
+ 'Clicked D',
+ 'B', // Ideally this should be later.
+ 'C',
+ 'Hover C',
+ 'A',
+ ]);
+ }
document.body.removeChild(container);
});
@@ -796,16 +870,18 @@ describe('ReactDOMServerSelectiveHydration', () => {
dispatchMouseHoverEvent(spanB, spanD);
dispatchMouseHoverEvent(spanC, spanB);
- suspend = false;
- resolve();
- await promise;
+ await act(async () => {
+ suspend = false;
+ resolve();
+ await promise;
+ });
// We should prioritize hydrating D first because we clicked it.
// Next we should hydrate C since that's the current hover target.
// Next it doesn't matter if we hydrate A or B first but as an
// implementation detail we're currently hydrating B first since
// we at one point hovered over it and we never deprioritized it.
- expect(Scheduler).toFlushAndYield(['App', 'C', 'Hover C', 'A', 'B', 'D']);
+ expect(Scheduler).toHaveYielded(['App', 'C', 'Hover C', 'A', 'B', 'D']);
document.body.removeChild(container);
});
@@ -970,4 +1046,88 @@ describe('ReactDOMServerSelectiveHydration', () => {
document.body.removeChild(container);
});
+
+ // @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
+ it('fires capture event handlers and native events if content is hydratable during discrete event', async () => {
+ spyOnDev(console, 'error');
+ function Child({text}) {
+ Scheduler.unstable_yieldValue(text);
+ const ref = React.useRef();
+ React.useLayoutEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+ ref.current.onclick = () => {
+ Scheduler.unstable_yieldValue('Native Click ' + text);
+ };
+ }, [text]);
+ return (
+ {
+ Scheduler.unstable_yieldValue('Capture Clicked ' + text);
+ }}
+ onClick={e => {
+ Scheduler.unstable_yieldValue('Clicked ' + text);
+ }}>
+ {text}
+
+ );
+ }
+
+ function App() {
+ Scheduler.unstable_yieldValue('App');
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+ let finalHTML;
+ expect(() => {
+ finalHTML = ReactDOMServer.renderToString();
+ }).toErrorDev([
+ 'useLayoutEffect does nothing on the server',
+ 'useLayoutEffect does nothing on the server',
+ ]);
+
+ expect(Scheduler).toHaveYielded(['App', 'A', 'B']);
+
+ const container = document.createElement('div');
+ // We need this to be in the document since we'll dispatch events on it.
+ document.body.appendChild(container);
+
+ container.innerHTML = finalHTML;
+
+ const span = container.getElementsByTagName('span')[1];
+
+ ReactDOM.hydrateRoot(container, );
+
+ // Nothing has been hydrated so far.
+ expect(Scheduler).toHaveYielded([]);
+
+ // This should synchronously hydrate the root App and the second suspense
+ // boundary.
+ dispatchClickEvent(span);
+
+ // We rendered App, B and then invoked the event without rendering A.
+ expect(Scheduler).toHaveYielded([
+ 'App',
+ 'B',
+ 'Capture Clicked B',
+ 'Native Click B',
+ 'Clicked B',
+ ]);
+
+ // After continuing the scheduler, we finally hydrate A.
+ expect(Scheduler).toFlushAndYield(['A']);
+
+ document.body.removeChild(container);
+ });
});
diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js
index dfe3c2e3c00c0..6467421d4f635 100644
--- a/packages/react-dom/src/events/ReactDOMEventListener.js
+++ b/packages/react-dom/src/events/ReactDOMEventListener.js
@@ -11,13 +11,18 @@ import type {AnyNativeEvent} from '../events/PluginModuleType';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
import type {DOMEventName} from '../events/DOMEventNames';
-
+import {
+ enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ enableSelectiveHydration,
+} from 'shared/ReactFeatureFlags';
import {
isReplayableDiscreteEvent,
queueDiscreteEvent,
hasQueuedDiscreteEvents,
clearIfContinuousEvent,
queueIfContinuousEvent,
+ attemptSynchronousHydration,
+ isCapturePhaseSynchronouslyHydratableEvent,
} from './ReactDOMEventReplaying';
import {
getNearestMountedFiber,
@@ -28,7 +33,10 @@ import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {type EventSystemFlags, IS_CAPTURE_PHASE} from './EventSystemFlags';
import getEventTarget from './getEventTarget';
-import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
+import {
+ getInstanceFromNode,
+ getClosestInstanceFromNode,
+} from '../client/ReactDOMComponentTree';
import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem';
@@ -176,7 +184,7 @@ export function dispatchEvent(
return;
}
- const blockedOn = attemptToDispatchEvent(
+ let blockedOn = attemptToDispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
@@ -192,7 +200,10 @@ export function dispatchEvent(
}
if (allowReplay) {
- if (isReplayableDiscreteEvent(domEventName)) {
+ if (
+ !enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
+ isReplayableDiscreteEvent(domEventName)
+ ) {
// This this to be replayed later once the target is available.
queueDiscreteEvent(
blockedOn,
@@ -219,6 +230,29 @@ export function dispatchEvent(
clearIfContinuousEvent(domEventName, nativeEvent);
}
+ if (
+ enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
+ enableSelectiveHydration &&
+ isCapturePhaseSynchronouslyHydratableEvent(domEventName)
+ ) {
+ while (blockedOn !== null) {
+ const fiber = getInstanceFromNode(blockedOn);
+ if (fiber !== null) {
+ attemptSynchronousHydration(fiber);
+ }
+ const nextBlockedOn = attemptToDispatchEvent(
+ domEventName,
+ eventSystemFlags,
+ targetContainer,
+ nativeEvent,
+ );
+ if (nextBlockedOn === blockedOn) {
+ break;
+ }
+ blockedOn = nextBlockedOn;
+ }
+ }
+
// This is not replayable so we'll invoke it but without a target,
// in case the event system needs to trace it.
dispatchEventForPluginEventSystem(
diff --git a/packages/react-dom/src/events/ReactDOMEventReplaying.js b/packages/react-dom/src/events/ReactDOMEventReplaying.js
index eab9b4650c108..c363721bcd848 100644
--- a/packages/react-dom/src/events/ReactDOMEventReplaying.js
+++ b/packages/react-dom/src/events/ReactDOMEventReplaying.js
@@ -14,7 +14,10 @@ import type {EventSystemFlags} from './EventSystemFlags';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
-import {enableSelectiveHydration} from 'shared/ReactFeatureFlags';
+import {
+ enableSelectiveHydration,
+ enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+} from 'shared/ReactFeatureFlags';
import {
unstable_scheduleCallback as scheduleCallback,
unstable_NormalPriority as NormalPriority,
@@ -32,10 +35,14 @@ import {
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
-let attemptSynchronousHydration: (fiber: Object) => void;
+let _attemptSynchronousHydration: (fiber: Object) => void;
export function setAttemptSynchronousHydration(fn: (fiber: Object) => void) {
- attemptSynchronousHydration = fn;
+ _attemptSynchronousHydration = fn;
+}
+
+export function attemptSynchronousHydration(fiber: Object) {
+ _attemptSynchronousHydration(fiber);
}
let attemptDiscreteHydration: (fiber: Object) => void;
@@ -180,6 +187,9 @@ export function queueDiscreteEvent(
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
+ if (enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay) {
+ return;
+ }
const queuedEvent = createQueuedReplayableEvent(
blockedOn,
domEventName,
@@ -290,6 +300,13 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
return existingQueuedEvent;
}
+export function isCapturePhaseSynchronouslyHydratableEvent(
+ eventName: DOMEventName,
+) {
+ // TODO: maybe include more events
+ return isReplayableDiscreteEvent(eventName);
+}
+
export function queueIfContinuousEvent(
blockedOn: null | Container | SuspenseInstance,
domEventName: DOMEventName,
@@ -483,39 +500,41 @@ function attemptReplayContinuousQueuedEventInMap(
function replayUnblockedEvents() {
hasScheduledReplayAttempt = false;
- // First replay discrete events.
- while (queuedDiscreteEvents.length > 0) {
- const nextDiscreteEvent = queuedDiscreteEvents[0];
- if (nextDiscreteEvent.blockedOn !== null) {
- // We're still blocked.
- // Increase the priority of this boundary to unblock
- // the next discrete event.
- const fiber = getInstanceFromNode(nextDiscreteEvent.blockedOn);
- if (fiber !== null) {
- attemptDiscreteHydration(fiber);
- }
- break;
- }
- const targetContainers = nextDiscreteEvent.targetContainers;
- while (targetContainers.length > 0) {
- const targetContainer = targetContainers[0];
- const nextBlockedOn = attemptToDispatchEvent(
- nextDiscreteEvent.domEventName,
- nextDiscreteEvent.eventSystemFlags,
- targetContainer,
- nextDiscreteEvent.nativeEvent,
- );
- if (nextBlockedOn !== null) {
- // We're still blocked. Try again later.
- nextDiscreteEvent.blockedOn = nextBlockedOn;
+ if (!enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay) {
+ // First replay discrete events.
+ while (queuedDiscreteEvents.length > 0) {
+ const nextDiscreteEvent = queuedDiscreteEvents[0];
+ if (nextDiscreteEvent.blockedOn !== null) {
+ // We're still blocked.
+ // Increase the priority of this boundary to unblock
+ // the next discrete event.
+ const fiber = getInstanceFromNode(nextDiscreteEvent.blockedOn);
+ if (fiber !== null) {
+ attemptDiscreteHydration(fiber);
+ }
break;
}
- // This target container was successfully dispatched. Try the next.
- targetContainers.shift();
- }
- if (nextDiscreteEvent.blockedOn === null) {
- // We've successfully replayed the first event. Let's try the next one.
- queuedDiscreteEvents.shift();
+ const targetContainers = nextDiscreteEvent.targetContainers;
+ while (targetContainers.length > 0) {
+ const targetContainer = targetContainers[0];
+ const nextBlockedOn = attemptToDispatchEvent(
+ nextDiscreteEvent.domEventName,
+ nextDiscreteEvent.eventSystemFlags,
+ targetContainer,
+ nextDiscreteEvent.nativeEvent,
+ );
+ if (nextBlockedOn !== null) {
+ // We're still blocked. Try again later.
+ nextDiscreteEvent.blockedOn = nextBlockedOn;
+ break;
+ }
+ // This target container was successfully dispatched. Try the next.
+ targetContainers.shift();
+ }
+ if (nextDiscreteEvent.blockedOn === null) {
+ // We've successfully replayed the first event. Let's try the next one.
+ queuedDiscreteEvents.shift();
+ }
}
}
// Next replay any continuous events.
diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
index 4f2df6fffc495..1b2bfbb0bef4d 100644
--- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
@@ -649,7 +649,16 @@ describe('DOMPluginEventSystem', () => {
// We're now full hydrated.
- expect(clicks).toBe(1);
+ if (
+ gate(
+ flags =>
+ flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
+ )
+ ) {
+ expect(clicks).toBe(0);
+ } else {
+ expect(clicks).toBe(1);
+ }
document.body.removeChild(parentContainer);
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 608db694be518..f5d34e2ff6539 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -103,6 +103,8 @@ export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
+
export const enableComponentStackLocations = true;
export const enableNewReconciler = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 9c3d45528c5af..5a1c1ed8d9c60 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -50,6 +50,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index bf96cc00e687c..3c11070d6ecb3 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -41,6 +41,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = false;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 0d2b226adf2f6..4b0457c219587 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -41,6 +41,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
index f760425ce03cb..2c54c1fb77c20 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
@@ -51,6 +51,7 @@ export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableStrictEffects = false;
export const createRootStrictEffectsByDefault = false;
export const enableUseRefAccessWarning = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index cb0efcc34bdcd..be60bcfbddb77 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -41,6 +41,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js
index d1cfdbb749565..76af047ab6d4e 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.js
@@ -41,6 +41,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = false;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js
index 62a10f475acaa..6fc6cf3c68367 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js
@@ -41,6 +41,7 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
export const warnAboutSpreadingKeyToJSX = false;
export const warnOnSubscriptionInsideStartTransition = false;
export const enableSuspenseAvoidThisFallback = false;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = false;
export const enableComponentStackLocations = true;
export const enableLegacyFBSupport = !__EXPERIMENTAL__;
export const enableFilterEmptyStringAttributesDOM = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
index 6400ef0422503..ef980529f3f21 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -27,6 +27,7 @@ export const enableLazyContextPropagation = __VARIANT__;
export const enableSyncDefaultUpdates = __VARIANT__;
export const consoleManagedByDevToolsDuringStrictMode = __VARIANT__;
export const warnOnSubscriptionInsideStartTransition = __VARIANT__;
+export const enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay = __VARIANT__;
// Enable this flag to help with concurrent mode debugging.
// It logs information to the console about React scheduling, rendering, and commit phases.
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index c5dde53578c95..0f1ce6eb1f9f7 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -32,6 +32,7 @@ export const {
enableLazyContextPropagation,
enableSyncDefaultUpdates,
warnOnSubscriptionInsideStartTransition,
+ enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
} = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build.