Use SyncLane for discrete event hydration (#21038)
Discrete event hydration doesn't need to be interruptible, since there's nothing higher priority than discrete events. So we can use SyncLane instead of a special hydration lane.
Andrew Clark committed
Mar 22, 2021 at 11:05 UTC
1fafac002838ad2d75ee9eca527c356b040285ff
5 files changed
+121
-105
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+63
-56
@@ -2187,17 +2187,17 @@ describe('ReactDOMServerPartialHydration', () => {
2187
expect(container.textContent).toBe('Click meHello');
2188
2189
// We're now partially hydrated.
2190
- a.click();
2190
+ await act(async () => {
2191
+ a.click();
2192
+ });
2193
expect(clicks).toBe(0);
2194
2195
// Resolving the promise so that rendering can complete.
2194
- suspend = false;
2195
- resolve();
2196
- await promise;
2197
-
2198
- Scheduler.unstable_flushAll();
2199
- jest.runAllTimers();
2200
-
2196
+ await act(async () => {
2197
+ suspend = false;
2198
+ resolve();
2199
+ await promise;
2200
+ });
2201
expect(clicks).toBe(1);
2202
2203
expect(container.textContent).toBe('Hello');
@@ -2270,18 +2270,19 @@ describe('ReactDOMServerPartialHydration', () => {
2270
jest.runAllTimers();
2271
2272
// We're now partially hydrated.
2273
- a.click();
2273
+ await act(async () => {
2274
+ a.click();
2275
+ });
2276
// We should not have invoked the event yet because we're not
2277
// yet hydrated.
2278
expect(onEvent).toHaveBeenCalledTimes(0);
2279
2280
// Resolving the promise so that rendering can complete.
2279
- suspend = false;
2280
- resolve();
2281
- await promise;
2282
-
2283
- Scheduler.unstable_flushAll();
2284
- jest.runAllTimers();
2281
+ await act(async () => {
2282
+ suspend = false;
2283
+ resolve();
2284
+ await promise;
2285
+ });
2286
2287
expect(onEvent).toHaveBeenCalledTimes(2);
2288
@@ -2344,7 +2345,9 @@ describe('ReactDOMServerPartialHydration', () => {
2345
root.render(<App />);
2346
2347
// We'll do one click before hydrating.
2347
- a.click();
2348
+ await act(async () => {
2349
+ a.click();
2350
+ });
2351
// This should be delayed.
2352
expect(clicks).toBe(0);
2353
@@ -2352,17 +2355,17 @@ describe('ReactDOMServerPartialHydration', () => {
2355
jest.runAllTimers();
2356
2357
// We're now partially hydrated.
2355
- a.click();
2358
+ await act(async () => {
2359
+ a.click();
2360
+ });
2361
expect(clicks).toBe(0);
2362
2363
// Resolving the promise so that rendering can complete.
2359
- suspend = false;
2360
- resolve();
2361
- await promise;
2362
-
2363
- Scheduler.unstable_flushAll();
2364
- jest.runAllTimers();
2365
-
2364
+ await act(async () => {
2365
+ suspend = false;
2366
+ resolve();
2367
+ await promise;
2368
+ });
2369
expect(clicks).toBe(2);
2370
2371
document.body.removeChild(container);
@@ -2436,19 +2439,19 @@ describe('ReactDOMServerPartialHydration', () => {
2439
jest.runAllTimers();
2440
2441
// We're now partially hydrated.
2439
- a.click();
2442
+ await act(async () => {
2443
+ a.click();
2444
+ });
2445
// We should not have invoked the event yet because we're not
2446
// yet hydrated.
2447
expect(onEvent).toHaveBeenCalledTimes(0);
2448
2449
// Resolving the promise so that rendering can complete.
2445
- suspend = false;
2446
- resolve();
2447
- await promise;
2448
-
2449
- Scheduler.unstable_flushAll();
2450
- jest.runAllTimers();
2451
-
2450
+ await act(async () => {
2451
+ suspend = false;
2452
+ resolve();
2453
+ await promise;
2454
+ });
2455
expect(onEvent).toHaveBeenCalledTimes(2);
2456
2457
document.body.removeChild(container);
@@ -2510,17 +2513,18 @@ describe('ReactDOMServerPartialHydration', () => {
2513
jest.runAllTimers();
2514
2515
// We're now partially hydrated.
2513
- span.click();
2516
+ await act(async () => {
2517
+ span.click();
2518
+ });
2519
expect(clicksOnChild).toBe(0);
2520
expect(clicksOnParent).toBe(0);
2521
2522
// Resolving the promise so that rendering can complete.
2518
- suspend = false;
2519
- resolve();
2520
- await promise;
2521
-
2522
- Scheduler.unstable_flushAll();
2523
- jest.runAllTimers();
2523
+ await act(async () => {
2524
+ suspend = false;
2525
+ resolve();
2526
+ await promise;
2527
+ });
2528
2529
expect(clicksOnChild).toBe(1);
2530
// This will be zero due to the stopPropagation.
@@ -2589,16 +2593,17 @@ describe('ReactDOMServerPartialHydration', () => {
2593
Scheduler.unstable_flushAll();
2594
2595
// The Suspense boundary is not yet hydrated.
2592
- a.click();
2596
+ await act(async () => {
2597
+ a.click();
2598
+ });
2599
expect(clicks).toBe(0);
2600
2601
// Resolving the promise so that rendering can complete.
2596
- suspend = false;
2597
- resolve();
2598
- await promise;
2599
-
2600
- Scheduler.unstable_flushAll();
2601
- jest.runAllTimers();
2602
+ await act(async () => {
2603
+ suspend = false;
2604
+ resolve();
2605
+ await promise;
2606
+ });
2607
2608
// We're now full hydrated.
2609
@@ -2858,20 +2863,22 @@ describe('ReactDOMServerPartialHydration', () => {
2863
expect(container.textContent).toBe('Click meHello');
2864
2865
// We're now partially hydrated.
2861
- form.dispatchEvent(
2862
- new Event('submit', {
2863
- bubbles: true,
2864
- }),
2865
- );
2866
+ await act(async () => {
2867
+ form.dispatchEvent(
2868
+ new Event('submit', {
2869
+ bubbles: true,
2870
+ }),
2871
+ );
2872
+ });
2873
expect(submits).toBe(0);
2874
2875
// Resolving the promise so that rendering can complete.
2869
- suspend = false;
2870
- resolve();
2871
- await promise;
2876
+ await act(async () => {
2877
+ suspend = false;
2878
+ resolve();
2879
+ await promise;
2880
+ });
2881
2873
- Scheduler.unstable_flushAll();
2874
- jest.runAllTimers();
2882
expect(submits).toBe(1);
2883
expect(container.textContent).toBe('Hello');
2884
document.body.removeChild(container);
packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+45
-36
@@ -255,22 +255,25 @@ describe('ReactDOMServerSelectiveHydration', () => {
255
expect(Scheduler).toHaveYielded([]);
256
257
// This click target cannot be hydrated yet because it's suspended.
258
- const result = dispatchClickEvent(spanD);
259
-
260
- expect(Scheduler).toHaveYielded(['App']);
261
-
262
- expect(result).toBe(true);
263
-
264
- // Continuing rendering will render B next.
265
- expect(Scheduler).toFlushAndYield(['B', 'C']);
266
-
267
- suspend = false;
268
- resolve();
269
- await promise;
258
+ await act(async () => {
259
+ const result = dispatchClickEvent(spanD);
260
+ expect(result).toBe(true);
261
+ });
262
+ expect(Scheduler).toHaveYielded([
263
+ 'App',
264
+ // Continuing rendering will render B next.
265
+ 'B',
266
+ 'C',
267
+ ]);
268
269
+ await act(async () => {
270
+ suspend = false;
271
+ resolve();
272
+ await promise;
273
+ });
274
// After the click, we should prioritize D and the Click first,
275
// and only after that render A and C.
273
- expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
276
+ expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
277
278
document.body.removeChild(container);
279
});
@@ -348,13 +351,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
351
352
expect(Scheduler).toHaveYielded(['App']);
353
351
- suspend = false;
352
- resolve();
353
- await promise;
354
+ await act(async () => {
355
+ suspend = false;
356
+ resolve();
357
+ await promise;
358
+ });
359
360
// We should prioritize hydrating A, C and D first since we clicked in
361
// them. Only after they're done will we hydrate B.
357
- expect(Scheduler).toFlushAndYield([
362
+ expect(Scheduler).toHaveYielded([
363
'A',
364
'Clicked A',
365
'C',
@@ -506,21 +511,21 @@ describe('ReactDOMServerSelectiveHydration', () => {
511
// Nothing has been hydrated so far.
512
expect(Scheduler).toHaveYielded([]);
513
509
- const target = createEventTarget(spanD);
510
- target.virtualclick();
511
-
512
- expect(Scheduler).toHaveYielded(['App']);
513
-
514
// Continuing rendering will render B next.
515
- expect(Scheduler).toFlushAndYield(['B', 'C']);
516
-
517
- suspend = false;
518
- resolve();
519
- await promise;
515
+ await act(async () => {
516
+ const target = createEventTarget(spanD);
517
+ target.virtualclick();
518
+ });
519
+ expect(Scheduler).toHaveYielded(['App', 'B', 'C']);
520
521
// After the click, we should prioritize D and the Click first,
522
// and only after that render A and C.
523
- expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
523
+ await act(async () => {
524
+ suspend = false;
525
+ resolve();
526
+ await promise;
527
+ });
528
+ expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
529
530
document.body.removeChild(container);
531
});
@@ -602,13 +607,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
607
608
expect(Scheduler).toHaveYielded(['App']);
609
605
- suspend = false;
606
- resolve();
607
- await promise;
610
+ await act(async () => {
611
+ suspend = false;
612
+ resolve();
613
+ await promise;
614
+ });
615
616
// We should prioritize hydrating A, C and D first since we clicked in
617
// them. Only after they're done will we hydrate B.
611
- expect(Scheduler).toFlushAndYield([
618
+ expect(Scheduler).toHaveYielded([
619
'A',
620
'Clicked A',
621
'C',
@@ -701,9 +708,11 @@ describe('ReactDOMServerSelectiveHydration', () => {
708
709
expect(Scheduler).toHaveYielded(['App']);
710
704
- suspend = false;
705
- resolve();
706
- await promise;
711
+ await act(async () => {
712
+ suspend = false;
713
+ resolve();
714
+ await promise;
715
+ });
716
717
// We should prioritize hydrating D first because we clicked it.
718
// Next we should hydrate C since that's the current hover target.
@@ -711,7 +720,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
720
// the same time since B was already scheduled.
721
// This is ok because it will at least not continue for nested
722
// boundary. See the next test below.
714
- expect(Scheduler).toFlushAndYield([
723
+ expect(Scheduler).toHaveYielded([
724
'D',
725
'Clicked D',
726
'B', // Ideally this should be later.
packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+9
-7
@@ -67,6 +67,7 @@ describe('DOMPluginEventSystem', () => {
67
ReactDOM = require('react-dom');
68
Scheduler = require('scheduler');
69
ReactDOMServer = require('react-dom/server');
70
+ act = require('react-dom/test-utils').unstable_concurrentAct;
71
container = document.createElement('div');
72
document.body.appendChild(container);
73
startNativeEventListenerClearDown();
@@ -636,16 +637,17 @@ describe('DOMPluginEventSystem', () => {
637
Scheduler.unstable_flushAll();
638
639
// The Suspense boundary is not yet hydrated.
639
- a.click();
640
+ await act(async () => {
641
+ a.click();
642
+ });
643
expect(clicks).toBe(0);
644
645
// Resolving the promise so that rendering can complete.
643
- suspend = false;
644
- resolve();
645
- await promise;
646
-
647
- Scheduler.unstable_flushAll();
648
- jest.runAllTimers();
646
+ await act(async () => {
647
+ suspend = false;
648
+ resolve();
649
+ await promise;
650
+ });
651
652
// We're now full hydrated.
653
packages/react-reconciler/src/ReactFiberReconciler.new.js
+2
-3
@@ -78,7 +78,6 @@ import {
78
import {StrictLegacyMode} from './ReactTypeOfMode';
79
import {
80
SyncLane,
81
- InputDiscreteHydrationLane,
81
SelectiveHydrationLane,
82
NoTimestamp,
83
getHighestPriorityPendingLanes,
@@ -388,7 +387,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
387
// If we're still blocked after this, we need to increase
388
// the priority of any promises resolving within this
389
// boundary so that they next attempt also has higher pri.
391
- const retryLane = InputDiscreteHydrationLane;
390
+ const retryLane = SyncLane;
391
markRetryLaneIfNotHydrated(fiber, retryLane);
392
break;
393
}
@@ -422,7 +421,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
421
return;
422
}
423
const eventTime = requestEventTime();
425
- const lane = InputDiscreteHydrationLane;
424
+ const lane = SyncLane;
425
scheduleUpdateOnFiber(fiber, lane, eventTime);
426
markRetryLaneIfNotHydrated(fiber, lane);
427
}
packages/react-reconciler/src/ReactFiberReconciler.old.js
+2
-3
@@ -78,7 +78,6 @@ import {
78
import {StrictLegacyMode} from './ReactTypeOfMode';
79
import {
80
SyncLane,
81
- InputDiscreteHydrationLane,
81
SelectiveHydrationLane,
82
NoTimestamp,
83
getHighestPriorityPendingLanes,
@@ -388,7 +387,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
387
// If we're still blocked after this, we need to increase
388
// the priority of any promises resolving within this
389
// boundary so that they next attempt also has higher pri.
391
- const retryLane = InputDiscreteHydrationLane;
390
+ const retryLane = SyncLane;
391
markRetryLaneIfNotHydrated(fiber, retryLane);
392
break;
393
}
@@ -422,7 +421,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
421
return;
422
}
423
const eventTime = requestEventTime();
425
- const lane = InputDiscreteHydrationLane;
424
+ const lane = SyncLane;
425
scheduleUpdateOnFiber(fiber, lane, eventTime);
426
markRetryLaneIfNotHydrated(fiber, lane);
427
}