Skip to content

Commit 1fafac0

Browse files
authored
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.
1 parent 6d3ecb7 commit 1fafac0

File tree

5 files changed

+121
-105
lines changed

5 files changed

+121
-105
lines changed

packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js

Lines changed: 63 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,17 +2187,17 @@ describe('ReactDOMServerPartialHydration', () => {
21872187
expect(container.textContent).toBe('Click meHello');
21882188

21892189
// We're now partially hydrated.
2190-
a.click();
2190+
await act(async () => {
2191+
a.click();
2192+
});
21912193
expect(clicks).toBe(0);
21922194

21932195
// 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+
});
22012201
expect(clicks).toBe(1);
22022202

22032203
expect(container.textContent).toBe('Hello');
@@ -2270,18 +2270,19 @@ describe('ReactDOMServerPartialHydration', () => {
22702270
jest.runAllTimers();
22712271

22722272
// We're now partially hydrated.
2273-
a.click();
2273+
await act(async () => {
2274+
a.click();
2275+
});
22742276
// We should not have invoked the event yet because we're not
22752277
// yet hydrated.
22762278
expect(onEvent).toHaveBeenCalledTimes(0);
22772279

22782280
// 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+
});
22852286

22862287
expect(onEvent).toHaveBeenCalledTimes(2);
22872288

@@ -2344,25 +2345,27 @@ describe('ReactDOMServerPartialHydration', () => {
23442345
root.render(<App />);
23452346

23462347
// We'll do one click before hydrating.
2347-
a.click();
2348+
await act(async () => {
2349+
a.click();
2350+
});
23482351
// This should be delayed.
23492352
expect(clicks).toBe(0);
23502353

23512354
Scheduler.unstable_flushAll();
23522355
jest.runAllTimers();
23532356

23542357
// We're now partially hydrated.
2355-
a.click();
2358+
await act(async () => {
2359+
a.click();
2360+
});
23562361
expect(clicks).toBe(0);
23572362

23582363
// 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+
});
23662369
expect(clicks).toBe(2);
23672370

23682371
document.body.removeChild(container);
@@ -2436,19 +2439,19 @@ describe('ReactDOMServerPartialHydration', () => {
24362439
jest.runAllTimers();
24372440

24382441
// We're now partially hydrated.
2439-
a.click();
2442+
await act(async () => {
2443+
a.click();
2444+
});
24402445
// We should not have invoked the event yet because we're not
24412446
// yet hydrated.
24422447
expect(onEvent).toHaveBeenCalledTimes(0);
24432448

24442449
// 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+
});
24522455
expect(onEvent).toHaveBeenCalledTimes(2);
24532456

24542457
document.body.removeChild(container);
@@ -2510,17 +2513,18 @@ describe('ReactDOMServerPartialHydration', () => {
25102513
jest.runAllTimers();
25112514

25122515
// We're now partially hydrated.
2513-
span.click();
2516+
await act(async () => {
2517+
span.click();
2518+
});
25142519
expect(clicksOnChild).toBe(0);
25152520
expect(clicksOnParent).toBe(0);
25162521

25172522
// 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+
});
25242528

25252529
expect(clicksOnChild).toBe(1);
25262530
// This will be zero due to the stopPropagation.
@@ -2589,16 +2593,17 @@ describe('ReactDOMServerPartialHydration', () => {
25892593
Scheduler.unstable_flushAll();
25902594

25912595
// The Suspense boundary is not yet hydrated.
2592-
a.click();
2596+
await act(async () => {
2597+
a.click();
2598+
});
25932599
expect(clicks).toBe(0);
25942600

25952601
// 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+
});
26022607

26032608
// We're now full hydrated.
26042609

@@ -2858,20 +2863,22 @@ describe('ReactDOMServerPartialHydration', () => {
28582863
expect(container.textContent).toBe('Click meHello');
28592864

28602865
// 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+
});
28662873
expect(submits).toBe(0);
28672874

28682875
// 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+
});
28722881

2873-
Scheduler.unstable_flushAll();
2874-
jest.runAllTimers();
28752882
expect(submits).toBe(1);
28762883
expect(container.textContent).toBe('Hello');
28772884
document.body.removeChild(container);

packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -255,22 +255,25 @@ describe('ReactDOMServerSelectiveHydration', () => {
255255
expect(Scheduler).toHaveYielded([]);
256256

257257
// 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+
]);
270268

269+
await act(async () => {
270+
suspend = false;
271+
resolve();
272+
await promise;
273+
});
271274
// After the click, we should prioritize D and the Click first,
272275
// and only after that render A and C.
273-
expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']);
276+
expect(Scheduler).toHaveYielded(['D', 'Clicked D', 'A']);
274277

275278
document.body.removeChild(container);
276279
});
@@ -348,13 +351,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
348351

349352
expect(Scheduler).toHaveYielded(['App']);
350353

351-
suspend = false;
352-
resolve();
353-
await promise;
354+
await act(async () => {
355+
suspend = false;
356+
resolve();
357+
await promise;
358+
});
354359

355360
// We should prioritize hydrating A, C and D first since we clicked in
356361
// them. Only after they're done will we hydrate B.
357-
expect(Scheduler).toFlushAndYield([
362+
expect(Scheduler).toHaveYielded([
358363
'A',
359364
'Clicked A',
360365
'C',
@@ -506,21 +511,21 @@ describe('ReactDOMServerSelectiveHydration', () => {
506511
// Nothing has been hydrated so far.
507512
expect(Scheduler).toHaveYielded([]);
508513

509-
const target = createEventTarget(spanD);
510-
target.virtualclick();
511-
512-
expect(Scheduler).toHaveYielded(['App']);
513-
514514
// 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']);
520520

521521
// After the click, we should prioritize D and the Click first,
522522
// 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']);
524529

525530
document.body.removeChild(container);
526531
});
@@ -602,13 +607,15 @@ describe('ReactDOMServerSelectiveHydration', () => {
602607

603608
expect(Scheduler).toHaveYielded(['App']);
604609

605-
suspend = false;
606-
resolve();
607-
await promise;
610+
await act(async () => {
611+
suspend = false;
612+
resolve();
613+
await promise;
614+
});
608615

609616
// We should prioritize hydrating A, C and D first since we clicked in
610617
// them. Only after they're done will we hydrate B.
611-
expect(Scheduler).toFlushAndYield([
618+
expect(Scheduler).toHaveYielded([
612619
'A',
613620
'Clicked A',
614621
'C',
@@ -701,17 +708,19 @@ describe('ReactDOMServerSelectiveHydration', () => {
701708

702709
expect(Scheduler).toHaveYielded(['App']);
703710

704-
suspend = false;
705-
resolve();
706-
await promise;
711+
await act(async () => {
712+
suspend = false;
713+
resolve();
714+
await promise;
715+
});
707716

708717
// We should prioritize hydrating D first because we clicked it.
709718
// Next we should hydrate C since that's the current hover target.
710719
// To simplify implementation details we hydrate both B and C at
711720
// the same time since B was already scheduled.
712721
// This is ok because it will at least not continue for nested
713722
// boundary. See the next test below.
714-
expect(Scheduler).toFlushAndYield([
723+
expect(Scheduler).toHaveYielded([
715724
'D',
716725
'Clicked D',
717726
'B', // Ideally this should be later.

packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ describe('DOMPluginEventSystem', () => {
6767
ReactDOM = require('react-dom');
6868
Scheduler = require('scheduler');
6969
ReactDOMServer = require('react-dom/server');
70+
act = require('react-dom/test-utils').unstable_concurrentAct;
7071
container = document.createElement('div');
7172
document.body.appendChild(container);
7273
startNativeEventListenerClearDown();
@@ -636,16 +637,17 @@ describe('DOMPluginEventSystem', () => {
636637
Scheduler.unstable_flushAll();
637638

638639
// The Suspense boundary is not yet hydrated.
639-
a.click();
640+
await act(async () => {
641+
a.click();
642+
});
640643
expect(clicks).toBe(0);
641644

642645
// 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+
});
649651

650652
// We're now full hydrated.
651653

packages/react-reconciler/src/ReactFiberReconciler.new.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ import {
7878
import {StrictLegacyMode} from './ReactTypeOfMode';
7979
import {
8080
SyncLane,
81-
InputDiscreteHydrationLane,
8281
SelectiveHydrationLane,
8382
NoTimestamp,
8483
getHighestPriorityPendingLanes,
@@ -388,7 +387,7 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
388387
// If we're still blocked after this, we need to increase
389388
// the priority of any promises resolving within this
390389
// boundary so that they next attempt also has higher pri.
391-
const retryLane = InputDiscreteHydrationLane;
390+
const retryLane = SyncLane;
392391
markRetryLaneIfNotHydrated(fiber, retryLane);
393392
break;
394393
}
@@ -422,7 +421,7 @@ export function attemptDiscreteHydration(fiber: Fiber): void {
422421
return;
423422
}
424423
const eventTime = requestEventTime();
425-
const lane = InputDiscreteHydrationLane;
424+
const lane = SyncLane;
426425
scheduleUpdateOnFiber(fiber, lane, eventTime);
427426
markRetryLaneIfNotHydrated(fiber, lane);
428427
}

0 commit comments

Comments
 (0)