mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Test that discrete events that aren't hydratable do not propagate (#22502)
* test that discrete events that arent hydratable do not propagate * lint * feedback * feedback * lint * better test * nits * lint
This commit is contained in:
@@ -1130,4 +1130,67 @@ describe('ReactDOMServerSelectiveHydration', () => {
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
// @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
|
||||
it('does not propagate discrete event if it cannot be synchronously hydrated', async () => {
|
||||
let triggeredParent = false;
|
||||
let triggeredChild = false;
|
||||
let suspend = false;
|
||||
const promise = new Promise(() => {});
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
}
|
||||
Scheduler.unstable_yieldValue('Child');
|
||||
return (
|
||||
<span
|
||||
onClickCapture={e => {
|
||||
e.stopPropagation();
|
||||
triggeredChild = true;
|
||||
}}>
|
||||
Click me
|
||||
</span>
|
||||
);
|
||||
}
|
||||
function App() {
|
||||
const onClick = () => {
|
||||
triggeredParent = true;
|
||||
};
|
||||
Scheduler.unstable_yieldValue('App');
|
||||
return (
|
||||
<div
|
||||
ref={n => {
|
||||
if (n) n.onclick = onClick;
|
||||
}}
|
||||
onClick={onClick}>
|
||||
<Suspense fallback={null}>
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App', 'Child']);
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
suspend = true;
|
||||
|
||||
ReactDOM.hydrateRoot(container, <App />);
|
||||
// Nothing has been hydrated so far.
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
const span = container.getElementsByTagName('span')[0];
|
||||
dispatchClickEvent(span);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App']);
|
||||
|
||||
dispatchClickEvent(span);
|
||||
|
||||
expect(triggeredParent).toBe(false);
|
||||
expect(triggeredChild).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+10
-10
@@ -11,18 +11,14 @@ 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} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
|
||||
enableSelectiveHydration,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
isReplayableDiscreteEvent,
|
||||
isDiscreteEventThatRequiresHydration,
|
||||
queueDiscreteEvent,
|
||||
hasQueuedDiscreteEvents,
|
||||
clearIfContinuousEvent,
|
||||
queueIfContinuousEvent,
|
||||
attemptSynchronousHydration,
|
||||
isCapturePhaseSynchronouslyHydratableEvent,
|
||||
} from './ReactDOMEventReplaying';
|
||||
import {
|
||||
getNearestMountedFiber,
|
||||
@@ -169,7 +165,7 @@ export function dispatchEvent(
|
||||
if (
|
||||
allowReplay &&
|
||||
hasQueuedDiscreteEvents() &&
|
||||
isReplayableDiscreteEvent(domEventName)
|
||||
isDiscreteEventThatRequiresHydration(domEventName)
|
||||
) {
|
||||
// If we already have a queue of discrete events, and this is another discrete
|
||||
// event, then we can't dispatch it regardless of its target, since they
|
||||
@@ -202,7 +198,7 @@ export function dispatchEvent(
|
||||
if (allowReplay) {
|
||||
if (
|
||||
!enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
|
||||
isReplayableDiscreteEvent(domEventName)
|
||||
isDiscreteEventThatRequiresHydration(domEventName)
|
||||
) {
|
||||
// This this to be replayed later once the target is available.
|
||||
queueDiscreteEvent(
|
||||
@@ -232,8 +228,8 @@ export function dispatchEvent(
|
||||
|
||||
if (
|
||||
enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
|
||||
enableSelectiveHydration &&
|
||||
isCapturePhaseSynchronouslyHydratableEvent(domEventName)
|
||||
eventSystemFlags & IS_CAPTURE_PHASE &&
|
||||
isDiscreteEventThatRequiresHydration(domEventName)
|
||||
) {
|
||||
while (blockedOn !== null) {
|
||||
const fiber = getInstanceFromNode(blockedOn);
|
||||
@@ -251,6 +247,10 @@ export function dispatchEvent(
|
||||
}
|
||||
blockedOn = nextBlockedOn;
|
||||
}
|
||||
if (blockedOn) {
|
||||
nativeEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This is not replayable so we'll invoke it but without a target,
|
||||
|
||||
+3
-8
@@ -160,7 +160,9 @@ const discreteReplayableEvents: Array<DOMEventName> = [
|
||||
'submit',
|
||||
];
|
||||
|
||||
export function isReplayableDiscreteEvent(eventType: DOMEventName): boolean {
|
||||
export function isDiscreteEventThatRequiresHydration(
|
||||
eventType: DOMEventName,
|
||||
): boolean {
|
||||
return discreteReplayableEvents.indexOf(eventType) > -1;
|
||||
}
|
||||
|
||||
@@ -300,13 +302,6 @@ 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,
|
||||
|
||||
Reference in New Issue
Block a user