@samitouri / QOS-React-2 / commits / 6ecad79ccf

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

salazarm committed Oct 6, 2021 at 08:31 UTC 6ecad79ccf2c683cb21ac40dee5678358c7b6208
3 files changed +76 -18
packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+63
@@ -1130,4 +1130,67 @@ describe('ReactDOMServerSelectiveHydration', () => {
1130
1131 document.body.removeChild(container);
1132 });
1133 +
1134 + // @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
1135 + it('does not propagate discrete event if it cannot be synchronously hydrated', async () => {
1136 + let triggeredParent = false;
1137 + let triggeredChild = false;
1138 + let suspend = false;
1139 + const promise = new Promise(() => {});
1140 + function Child() {
1141 + if (suspend) {
1142 + throw promise;
1143 + }
1144 + Scheduler.unstable_yieldValue('Child');
1145 + return (
1146 + <span
1147 + onClickCapture={e => {
1148 + e.stopPropagation();
1149 + triggeredChild = true;
1150 + }}>
1151 + Click me
1152 + </span>
1153 + );
1154 + }
1155 + function App() {
1156 + const onClick = () => {
1157 + triggeredParent = true;
1158 + };
1159 + Scheduler.unstable_yieldValue('App');
1160 + return (
1161 + <div
1162 + ref={n => {
1163 + if (n) n.onclick = onClick;
1164 + }}
1165 + onClick={onClick}>
1166 + <Suspense fallback={null}>
1167 + <Child />
1168 + </Suspense>
1169 + </div>
1170 + );
1171 + }
1172 + const finalHTML = ReactDOMServer.renderToString(<App />);
1173 +
1174 + expect(Scheduler).toHaveYielded(['App', 'Child']);
1175 +
1176 + const container = document.createElement('div');
1177 + document.body.appendChild(container);
1178 + container.innerHTML = finalHTML;
1179 +
1180 + suspend = true;
1181 +
1182 + ReactDOM.hydrateRoot(container, <App />);
1183 + // Nothing has been hydrated so far.
1184 + expect(Scheduler).toHaveYielded([]);
1185 +
1186 + const span = container.getElementsByTagName('span')[0];
1187 + dispatchClickEvent(span);
1188 +
1189 + expect(Scheduler).toHaveYielded(['App']);
1190 +
1191 + dispatchClickEvent(span);
1192 +
1193 + expect(triggeredParent).toBe(false);
1194 + expect(triggeredChild).toBe(false);
1195 + });
1196 });
packages/react-dom/src/events/ReactDOMEventListener.js
+10 -10
@@ -11,18 +11,14 @@ import type {AnyNativeEvent} from '../events/PluginModuleType';
11 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
12 import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
13 import type {DOMEventName} from '../events/DOMEventNames';
14 +import {enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay} from 'shared/ReactFeatureFlags';
15 import {
15 - enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
16 - enableSelectiveHydration,
17 -} from 'shared/ReactFeatureFlags';
18 -import {
19 - isReplayableDiscreteEvent,
16 + isDiscreteEventThatRequiresHydration,
17 queueDiscreteEvent,
18 hasQueuedDiscreteEvents,
19 clearIfContinuousEvent,
20 queueIfContinuousEvent,
21 attemptSynchronousHydration,
25 - isCapturePhaseSynchronouslyHydratableEvent,
22 } from './ReactDOMEventReplaying';
23 import {
24 getNearestMountedFiber,
@@ -169,7 +165,7 @@ export function dispatchEvent(
165 if (
166 allowReplay &&
167 hasQueuedDiscreteEvents() &&
172 - isReplayableDiscreteEvent(domEventName)
168 + isDiscreteEventThatRequiresHydration(domEventName)
169 ) {
170 // If we already have a queue of discrete events, and this is another discrete
171 // event, then we can't dispatch it regardless of its target, since they
@@ -202,7 +198,7 @@ export function dispatchEvent(
198 if (allowReplay) {
199 if (
200 !enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
205 - isReplayableDiscreteEvent(domEventName)
201 + isDiscreteEventThatRequiresHydration(domEventName)
202 ) {
203 // This this to be replayed later once the target is available.
204 queueDiscreteEvent(
@@ -232,8 +228,8 @@ export function dispatchEvent(
228
229 if (
230 enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay &&
235 - enableSelectiveHydration &&
236 - isCapturePhaseSynchronouslyHydratableEvent(domEventName)
231 + eventSystemFlags & IS_CAPTURE_PHASE &&
232 + isDiscreteEventThatRequiresHydration(domEventName)
233 ) {
234 while (blockedOn !== null) {
235 const fiber = getInstanceFromNode(blockedOn);
@@ -251,6 +247,10 @@ export function dispatchEvent(
247 }
248 blockedOn = nextBlockedOn;
249 }
250 + if (blockedOn) {
251 + nativeEvent.stopPropagation();
252 + return;
253 + }
254 }
255
256 // This is not replayable so we'll invoke it but without a target,
packages/react-dom/src/events/ReactDOMEventReplaying.js
+3 -8
@@ -160,7 +160,9 @@ const discreteReplayableEvents: Array<DOMEventName> = [
160 'submit',
161 ];
162
163 -export function isReplayableDiscreteEvent(eventType: DOMEventName): boolean {
163 +export function isDiscreteEventThatRequiresHydration(
164 + eventType: DOMEventName,
165 +): boolean {
166 return discreteReplayableEvents.indexOf(eventType) > -1;
167 }
168
@@ -300,13 +302,6 @@ function accumulateOrCreateContinuousQueuedReplayableEvent(
302 return existingQueuedEvent;
303 }
304
303 -export function isCapturePhaseSynchronouslyHydratableEvent(
304 - eventName: DOMEventName,
305 -) {
306 - // TODO: maybe include more events
307 - return isReplayableDiscreteEvent(eventName);
308 -}
309 -
305 export function queueIfContinuousEvent(
306 blockedOn: null | Container | SuspenseInstance,
307 domEventName: DOMEventName,