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:
salazarm
2021-10-06 08:31:21 -04:00
committed by GitHub
parent 579c008a75
commit 6ecad79ccf
3 changed files with 76 additions and 18 deletions
@@ -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
View File
@@ -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
View File
@@ -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,