Fix: Don't flush discrete at end of batchedUpdates (#21229)
The outermost `batchedUpdates` call flushes pending sync updates at the end. This was intended for legacy sync mode, but it also happens to flush discrete updates in concurrent mode. Instead, we should only flush sync updates at the end of `batchedUpdates` for legacy roots. Discrete sync updates can wait to flush in the microtask. `discreteUpdates` has the same issue, which is how I originally noticed this, but I'll change that one in a separate commit since it requires updating a few (no longer relevant) internal tests.
Andrew Clark committed
Apr 21, 2021 at 11:32 UTC
a1558600183b4321d4cdc117925980f1865641fa
8 files changed
+198
-71
packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
+12
-8
@@ -11,6 +11,7 @@ describe('StoreStressConcurrent', () => {
11
let React;
12
let ReactDOM;
13
let act;
14
+ let actAsync;
15
let bridge;
16
let store;
17
let print;
@@ -23,6 +24,9 @@ describe('StoreStressConcurrent', () => {
24
React = require('react');
25
ReactDOM = require('react-dom');
26
act = require('./utils').act;
27
+ // TODO: Figure out recommendation for concurrent mode tests, then replace
28
+ // this helper with the real thing.
29
+ actAsync = require('./utils').actAsync;
30
31
print = require('./storeSerializer').print;
32
});
@@ -758,7 +762,7 @@ describe('StoreStressConcurrent', () => {
762
763
// Force fallback.
764
expect(print(store)).toEqual(snapshots[i]);
761
- act(() => {
765
+ await actAsync(async () => {
766
bridge.send('overrideSuspense', {
767
id: suspenseID,
768
rendererID: store.getRendererIDForElement(suspenseID),
@@ -768,7 +772,7 @@ describe('StoreStressConcurrent', () => {
772
expect(print(store)).toEqual(snapshots[j]);
773
774
// Stop forcing fallback.
771
- act(() => {
775
+ await actAsync(async () => {
776
bridge.send('overrideSuspense', {
777
id: suspenseID,
778
rendererID: store.getRendererIDForElement(suspenseID),
@@ -818,7 +822,7 @@ describe('StoreStressConcurrent', () => {
822
expect(print(store)).toEqual(snapshots[j]);
823
824
// Stop forcing fallback. This reverts to primary content.
821
- act(() => {
825
+ await actAsync(async () => {
826
bridge.send('overrideSuspense', {
827
id: suspenseID,
828
rendererID: store.getRendererIDForElement(suspenseID),
@@ -829,13 +833,13 @@ describe('StoreStressConcurrent', () => {
833
expect(print(store)).toEqual(snapshots[i]);
834
835
// Clean up after every iteration.
832
- act(() => root.unmount());
836
+ await actAsync(async () => root.unmount());
837
expect(print(store)).toBe('');
838
}
839
}
840
});
841
838
- it('should handle a stress test for Suspense without type change (Concurrent Mode)', () => {
842
+ it('should handle a stress test for Suspense without type change (Concurrent Mode)', async () => {
843
const A = () => 'a';
844
const B = () => 'b';
845
const C = () => 'c';
@@ -1294,7 +1298,7 @@ describe('StoreStressConcurrent', () => {
1298
1299
// Force fallback.
1300
expect(print(store)).toEqual(snapshots[i]);
1297
- act(() => {
1301
+ await actAsync(async () => {
1302
bridge.send('overrideSuspense', {
1303
id: suspenseID,
1304
rendererID: store.getRendererIDForElement(suspenseID),
@@ -1304,7 +1308,7 @@ describe('StoreStressConcurrent', () => {
1308
expect(print(store)).toEqual(fallbackSnapshots[j]);
1309
1310
// Stop forcing fallback.
1307
- act(() => {
1311
+ await actAsync(async () => {
1312
bridge.send('overrideSuspense', {
1313
id: suspenseID,
1314
rendererID: store.getRendererIDForElement(suspenseID),
@@ -1354,7 +1358,7 @@ describe('StoreStressConcurrent', () => {
1358
expect(print(store)).toEqual(fallbackSnapshots[j]);
1359
1360
// Stop forcing fallback. This reverts to primary content.
1357
- act(() => {
1361
+ await actAsync(async () => {
1362
bridge.send('overrideSuspense', {
1363
id: suspenseID,
1364
rendererID: store.getRendererIDForElement(suspenseID),
packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
+50
@@ -371,4 +371,54 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
371
);
372
}
373
});
374
+
375
+ // @gate experimental
376
+ it('should not flush discrete events at the end of outermost batchedUpdates', async () => {
377
+ const root = ReactDOM.unstable_createRoot(container);
378
+
379
+ let target;
380
+ function Foo() {
381
+ const [count, setCount] = React.useState(0);
382
+ return (
383
+ <div
384
+ ref={el => {
385
+ target = el;
386
+ if (target !== null) {
387
+ el.onclick = () => {
388
+ ReactDOM.unstable_batchedUpdates(() => {
389
+ setCount(count + 1);
390
+ });
391
+ Scheduler.unstable_yieldValue(
392
+ container.textContent + ' [after batchedUpdates]',
393
+ );
394
+ };
395
+ }
396
+ }}>
397
+ Count: {count}
398
+ </div>
399
+ );
400
+ }
401
+
402
+ await act(async () => {
403
+ root.render(<Foo />);
404
+ });
405
+ expect(container.textContent).toEqual('Count: 0');
406
+
407
+ const pressEvent = document.createEvent('Event');
408
+ pressEvent.initEvent('click', true, true);
409
+ dispatchAndSetCurrentEvent(target, pressEvent);
410
+
411
+ expect(Scheduler).toHaveYielded(['Count: 0 [after batchedUpdates]']);
412
+ // TODO: There's a `flushDiscreteUpdates` call at the end of the event
413
+ // delegation listener that gets called even if no React event handlers are
414
+ // fired. Once that is removed, this will be 0, not 1.
415
+ // expect(container.textContent).toEqual('Count: 0');
416
+ expect(container.textContent).toEqual('Count: 1');
417
+
418
+ // Intentionally not using `act` so we can observe in between the click
419
+ // event and the microtask, without batching.
420
+ await null;
421
+
422
+ expect(container.textContent).toEqual('Count: 1');
423
+ });
424
});
packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js
+20
-2
@@ -17,6 +17,7 @@ import {
17
import {ImmediatePriority, scheduleCallback} from './Scheduler';
18
19
let syncQueue: Array<SchedulerCallback> | null = null;
20
+let includesLegacySyncCallbacks: boolean = false;
21
let isFlushingSyncQueue: boolean = false;
22
23
export function scheduleSyncCallback(callback: SchedulerCallback) {
@@ -31,7 +32,23 @@ export function scheduleSyncCallback(callback: SchedulerCallback) {
32
}
33
}
34
34
-export function flushSyncCallbackQueue() {
35
+export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
36
+ includesLegacySyncCallbacks = true;
37
+ scheduleSyncCallback(callback);
38
+}
39
+
40
+export function flushSyncCallbacksOnlyInLegacyMode() {
41
+ // Only flushes the queue if there's a legacy sync callback scheduled.
42
+ // TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
43
+ // it might make more sense for the queue to be a list of roots instead of a
44
+ // list of generic callbacks. Then we can have two: one for legacy roots, one
45
+ // for concurrent roots. And this method would only flush the legacy ones.
46
+ if (includesLegacySyncCallbacks) {
47
+ flushSyncCallbacks();
48
+ }
49
+}
50
+
51
+export function flushSyncCallbacks() {
52
if (!isFlushingSyncQueue && syncQueue !== null) {
53
// Prevent re-entrancy.
54
isFlushingSyncQueue = true;
@@ -50,13 +67,14 @@ export function flushSyncCallbackQueue() {
67
} while (callback !== null);
68
}
69
syncQueue = null;
70
+ includesLegacySyncCallbacks = false;
71
} catch (error) {
72
// If something throws, leave the remaining callbacks on the queue.
73
if (syncQueue !== null) {
74
syncQueue = syncQueue.slice(i + 1);
75
}
76
// Resume flushing in the next tick
59
- scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
77
+ scheduleCallback(ImmediatePriority, flushSyncCallbacks);
78
throw error;
79
} finally {
80
setCurrentUpdatePriority(previousUpdatePriority);
packages/react-reconciler/src/ReactFiberSyncTaskQueue.old.js
+20
-2
@@ -17,6 +17,7 @@ import {
17
import {ImmediatePriority, scheduleCallback} from './Scheduler';
18
19
let syncQueue: Array<SchedulerCallback> | null = null;
20
+let includesLegacySyncCallbacks: boolean = false;
21
let isFlushingSyncQueue: boolean = false;
22
23
export function scheduleSyncCallback(callback: SchedulerCallback) {
@@ -31,7 +32,23 @@ export function scheduleSyncCallback(callback: SchedulerCallback) {
32
}
33
}
34
34
-export function flushSyncCallbackQueue() {
35
+export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
36
+ includesLegacySyncCallbacks = true;
37
+ scheduleSyncCallback(callback);
38
+}
39
+
40
+export function flushSyncCallbacksOnlyInLegacyMode() {
41
+ // Only flushes the queue if there's a legacy sync callback scheduled.
42
+ // TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
43
+ // it might make more sense for the queue to be a list of roots instead of a
44
+ // list of generic callbacks. Then we can have two: one for legacy roots, one
45
+ // for concurrent roots. And this method would only flush the legacy ones.
46
+ if (includesLegacySyncCallbacks) {
47
+ flushSyncCallbacks();
48
+ }
49
+}
50
+
51
+export function flushSyncCallbacks() {
52
if (!isFlushingSyncQueue && syncQueue !== null) {
53
// Prevent re-entrancy.
54
isFlushingSyncQueue = true;
@@ -50,13 +67,14 @@ export function flushSyncCallbackQueue() {
67
} while (callback !== null);
68
}
69
syncQueue = null;
70
+ includesLegacySyncCallbacks = false;
71
} catch (error) {
72
// If something throws, leave the remaining callbacks on the queue.
73
if (syncQueue !== null) {
74
syncQueue = syncQueue.slice(i + 1);
75
}
76
// Resume flushing in the next tick
59
- scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
77
+ scheduleCallback(ImmediatePriority, flushSyncCallbacks);
78
throw error;
79
} finally {
80
setCurrentUpdatePriority(previousUpdatePriority);
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+31
-19
@@ -49,8 +49,10 @@ import {
49
IdlePriority as IdleSchedulerPriority,
50
} from './Scheduler';
51
import {
52
- flushSyncCallbackQueue,
52
+ flushSyncCallbacks,
53
+ flushSyncCallbacksOnlyInLegacyMode,
54
scheduleSyncCallback,
55
+ scheduleLegacySyncCallback,
56
} from './ReactFiberSyncTaskQueue.new';
57
import {
58
NoFlags as NoHookEffect,
@@ -561,7 +563,7 @@ export function scheduleUpdateOnFiber(
563
// without immediately flushing it. We only do this for user-initiated
564
// updates, to preserve historical behavior of legacy mode.
565
resetRenderTimer();
564
- flushSyncCallbackQueue();
566
+ flushSyncCallbacksOnlyInLegacyMode();
567
}
568
}
569
} else {
@@ -698,13 +700,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
700
if (newCallbackPriority === SyncLane) {
701
// Special case: Sync React callbacks are scheduled on a special
702
// internal queue
701
- scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
703
+ if (root.tag === LegacyRoot) {
704
+ scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
705
+ } else {
706
+ scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
707
+ }
708
if (supportsMicrotasks) {
709
// Flush the queue in a microtask.
704
- scheduleMicrotask(flushSyncCallbackQueue);
710
+ scheduleMicrotask(flushSyncCallbacks);
711
} else {
712
// Flush the queue in an Immediate task.
707
- scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbackQueue);
713
+ scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
714
}
715
newCallbackNode = null;
716
} else {
@@ -1054,7 +1060,7 @@ export function flushRoot(root: FiberRoot, lanes: Lanes) {
1060
ensureRootIsScheduled(root, now());
1061
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1062
resetRenderTimer();
1057
- flushSyncCallbackQueue();
1063
+ flushSyncCallbacks();
1064
}
1065
}
1066
}
@@ -1085,7 +1091,7 @@ export function flushDiscreteUpdates() {
1091
// like `el.focus()`. Exit.
1092
return;
1093
}
1088
- flushSyncCallbackQueue();
1094
+ flushSyncCallbacks();
1095
// If the discrete updates scheduled passive effects, flush them now so that
1096
// they fire before the next serial event.
1097
flushPassiveEffects();
@@ -1111,10 +1117,11 @@ export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1117
return fn(a);
1118
} finally {
1119
executionContext = prevExecutionContext;
1120
+ // If there were legacy sync updates, flush them at the end of the outer
1121
+ // most batchedUpdates-like method.
1122
if (executionContext === NoContext) {
1115
- // Flush the immediate callbacks that were scheduled during this batch
1123
resetRenderTimer();
1117
- flushSyncCallbackQueue();
1124
+ flushSyncCallbacksOnlyInLegacyMode();
1125
}
1126
}
1127
}
@@ -1126,10 +1133,11 @@ export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1133
return fn(a);
1134
} finally {
1135
executionContext = prevExecutionContext;
1136
+ // If there were legacy sync updates, flush them at the end of the outer
1137
+ // most batchedUpdates-like method.
1138
if (executionContext === NoContext) {
1130
- // Flush the immediate callbacks that were scheduled during this batch
1139
resetRenderTimer();
1132
- flushSyncCallbackQueue();
1140
+ flushSyncCallbacksOnlyInLegacyMode();
1141
}
1142
}
1143
}
@@ -1151,9 +1159,10 @@ export function discreteUpdates<A, B, C, D, R>(
1159
setCurrentUpdatePriority(previousPriority);
1160
ReactCurrentBatchConfig.transition = prevTransition;
1161
if (executionContext === NoContext) {
1154
- // Flush the immediate callbacks that were scheduled during this batch
1162
resetRenderTimer();
1156
- flushSyncCallbackQueue();
1163
+ // TODO: This should only flush legacy sync updates. Not discrete updates
1164
+ // in Concurrent Mode. Discrete updates will flush in a microtask.
1165
+ flushSyncCallbacks();
1166
}
1167
}
1168
}
@@ -1166,10 +1175,13 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1175
return fn(a);
1176
} finally {
1177
executionContext = prevExecutionContext;
1178
+ // If there were legacy sync updates, flush them at the end of the outer
1179
+ // most batchedUpdates-like method.
1180
if (executionContext === NoContext) {
1170
- // Flush the immediate callbacks that were scheduled during this batch
1181
resetRenderTimer();
1172
- flushSyncCallbackQueue();
1182
+ // TODO: I think this call is redundant, because we flush inside
1183
+ // scheduleUpdateOnFiber when LegacyUnbatchedContext is set.
1184
+ flushSyncCallbacksOnlyInLegacyMode();
1185
}
1186
}
1187
}
@@ -1196,7 +1208,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1208
// Note that this will happen even if batchedUpdates is higher up
1209
// the stack.
1210
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1199
- flushSyncCallbackQueue();
1211
+ flushSyncCallbacks();
1212
} else {
1213
if (__DEV__) {
1214
console.error(
@@ -1226,7 +1238,7 @@ export function flushControlled(fn: () => mixed): void {
1238
if (executionContext === NoContext) {
1239
// Flush the immediate callbacks that were scheduled during this batch
1240
resetRenderTimer();
1229
- flushSyncCallbackQueue();
1241
+ flushSyncCallbacks();
1242
}
1243
}
1244
}
@@ -2098,7 +2110,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2110
}
2111
2112
// If layout work was scheduled, flush it now.
2101
- flushSyncCallbackQueue();
2113
+ flushSyncCallbacks();
2114
2115
if (__DEV__) {
2116
if (enableDebugTracing) {
@@ -2224,7 +2236,7 @@ function flushPassiveEffectsImpl() {
2236
2237
executionContext = prevExecutionContext;
2238
2227
- flushSyncCallbackQueue();
2239
+ flushSyncCallbacks();
2240
2241
// If additional passive effects were scheduled, increment a counter. If this
2242
// exceeds the limit, we'll fire a warning.
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+31
-19
@@ -49,8 +49,10 @@ import {
49
IdlePriority as IdleSchedulerPriority,
50
} from './Scheduler';
51
import {
52
- flushSyncCallbackQueue,
52
+ flushSyncCallbacks,
53
+ flushSyncCallbacksOnlyInLegacyMode,
54
scheduleSyncCallback,
55
+ scheduleLegacySyncCallback,
56
} from './ReactFiberSyncTaskQueue.old';
57
import {
58
NoFlags as NoHookEffect,
@@ -561,7 +563,7 @@ export function scheduleUpdateOnFiber(
563
// without immediately flushing it. We only do this for user-initiated
564
// updates, to preserve historical behavior of legacy mode.
565
resetRenderTimer();
564
- flushSyncCallbackQueue();
566
+ flushSyncCallbacksOnlyInLegacyMode();
567
}
568
}
569
} else {
@@ -698,13 +700,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
700
if (newCallbackPriority === SyncLane) {
701
// Special case: Sync React callbacks are scheduled on a special
702
// internal queue
701
- scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
703
+ if (root.tag === LegacyRoot) {
704
+ scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
705
+ } else {
706
+ scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
707
+ }
708
if (supportsMicrotasks) {
709
// Flush the queue in a microtask.
704
- scheduleMicrotask(flushSyncCallbackQueue);
710
+ scheduleMicrotask(flushSyncCallbacks);
711
} else {
712
// Flush the queue in an Immediate task.
707
- scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbackQueue);
713
+ scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
714
}
715
newCallbackNode = null;
716
} else {
@@ -1054,7 +1060,7 @@ export function flushRoot(root: FiberRoot, lanes: Lanes) {
1060
ensureRootIsScheduled(root, now());
1061
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1062
resetRenderTimer();
1057
- flushSyncCallbackQueue();
1063
+ flushSyncCallbacks();
1064
}
1065
}
1066
}
@@ -1085,7 +1091,7 @@ export function flushDiscreteUpdates() {
1091
// like `el.focus()`. Exit.
1092
return;
1093
}
1088
- flushSyncCallbackQueue();
1094
+ flushSyncCallbacks();
1095
// If the discrete updates scheduled passive effects, flush them now so that
1096
// they fire before the next serial event.
1097
flushPassiveEffects();
@@ -1111,10 +1117,11 @@ export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1117
return fn(a);
1118
} finally {
1119
executionContext = prevExecutionContext;
1120
+ // If there were legacy sync updates, flush them at the end of the outer
1121
+ // most batchedUpdates-like method.
1122
if (executionContext === NoContext) {
1115
- // Flush the immediate callbacks that were scheduled during this batch
1123
resetRenderTimer();
1117
- flushSyncCallbackQueue();
1124
+ flushSyncCallbacksOnlyInLegacyMode();
1125
}
1126
}
1127
}
@@ -1126,10 +1133,11 @@ export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1133
return fn(a);
1134
} finally {
1135
executionContext = prevExecutionContext;
1136
+ // If there were legacy sync updates, flush them at the end of the outer
1137
+ // most batchedUpdates-like method.
1138
if (executionContext === NoContext) {
1130
- // Flush the immediate callbacks that were scheduled during this batch
1139
resetRenderTimer();
1132
- flushSyncCallbackQueue();
1140
+ flushSyncCallbacksOnlyInLegacyMode();
1141
}
1142
}
1143
}
@@ -1151,9 +1159,10 @@ export function discreteUpdates<A, B, C, D, R>(
1159
setCurrentUpdatePriority(previousPriority);
1160
ReactCurrentBatchConfig.transition = prevTransition;
1161
if (executionContext === NoContext) {
1154
- // Flush the immediate callbacks that were scheduled during this batch
1162
resetRenderTimer();
1156
- flushSyncCallbackQueue();
1163
+ // TODO: This should only flush legacy sync updates. Not discrete updates
1164
+ // in Concurrent Mode. Discrete updates will flush in a microtask.
1165
+ flushSyncCallbacks();
1166
}
1167
}
1168
}
@@ -1166,10 +1175,13 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1175
return fn(a);
1176
} finally {
1177
executionContext = prevExecutionContext;
1178
+ // If there were legacy sync updates, flush them at the end of the outer
1179
+ // most batchedUpdates-like method.
1180
if (executionContext === NoContext) {
1170
- // Flush the immediate callbacks that were scheduled during this batch
1181
resetRenderTimer();
1172
- flushSyncCallbackQueue();
1182
+ // TODO: I think this call is redundant, because we flush inside
1183
+ // scheduleUpdateOnFiber when LegacyUnbatchedContext is set.
1184
+ flushSyncCallbacksOnlyInLegacyMode();
1185
}
1186
}
1187
}
@@ -1196,7 +1208,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1208
// Note that this will happen even if batchedUpdates is higher up
1209
// the stack.
1210
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1199
- flushSyncCallbackQueue();
1211
+ flushSyncCallbacks();
1212
} else {
1213
if (__DEV__) {
1214
console.error(
@@ -1226,7 +1238,7 @@ export function flushControlled(fn: () => mixed): void {
1238
if (executionContext === NoContext) {
1239
// Flush the immediate callbacks that were scheduled during this batch
1240
resetRenderTimer();
1229
- flushSyncCallbackQueue();
1241
+ flushSyncCallbacks();
1242
}
1243
}
1244
}
@@ -2098,7 +2110,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2110
}
2111
2112
// If layout work was scheduled, flush it now.
2101
- flushSyncCallbackQueue();
2113
+ flushSyncCallbacks();
2114
2115
if (__DEV__) {
2116
if (enableDebugTracing) {
@@ -2224,7 +2236,7 @@ function flushPassiveEffectsImpl() {
2236
2237
executionContext = prevExecutionContext;
2238
2227
- flushSyncCallbackQueue();
2239
+ flushSyncCallbacks();
2240
2241
// If additional passive effects were scheduled, increment a counter. If this
2242
// exceeds the limit, we'll fire a warning.
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+32
-18
@@ -13,16 +13,20 @@
13
let React;
14
let ReactFiberReconciler;
15
let ConcurrentRoot;
16
+let DefaultEventPriority;
17
18
describe('ReactFiberHostContext', () => {
19
beforeEach(() => {
20
jest.resetModules();
21
React = require('react');
22
ReactFiberReconciler = require('react-reconciler');
22
- ConcurrentRoot = require('react-reconciler/src/ReactRootTags');
23
+ ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
24
+ .ConcurrentRoot;
25
+ DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities')
26
+ .DefaultEventPriority;
27
});
28
25
- it('works with null host context', () => {
29
+ it('works with null host context', async () => {
30
let creates = 0;
31
const Renderer = ReactFiberReconciler({
32
prepareForCommit: function() {
@@ -54,6 +58,9 @@ describe('ReactFiberHostContext', () => {
58
return null;
59
},
60
clearContainer: function() {},
61
+ getCurrentEventPriority: function() {
62
+ return DefaultEventPriority;
63
+ },
64
supportsMutation: true,
65
});
66
@@ -63,14 +70,16 @@ describe('ReactFiberHostContext', () => {
70
false,
71
null,
72
);
66
- Renderer.updateContainer(
67
- <a>
68
- <b />
69
- </a>,
70
- container,
71
- /* parentComponent: */ null,
72
- /* callback: */ null,
73
- );
73
+ Renderer.act(() => {
74
+ Renderer.updateContainer(
75
+ <a>
76
+ <b />
77
+ </a>,
78
+ container,
79
+ /* parentComponent: */ null,
80
+ /* callback: */ null,
81
+ );
82
+ });
83
expect(creates).toBe(2);
84
});
85
@@ -109,6 +118,9 @@ describe('ReactFiberHostContext', () => {
118
return null;
119
},
120
clearContainer: function() {},
121
+ getCurrentEventPriority: function() {
122
+ return DefaultEventPriority;
123
+ },
124
supportsMutation: true,
125
});
126
@@ -118,13 +130,15 @@ describe('ReactFiberHostContext', () => {
130
false,
131
null,
132
);
121
- Renderer.updateContainer(
122
- <a>
123
- <b />
124
- </a>,
125
- container,
126
- /* parentComponent: */ null,
127
- /* callback: */ null,
128
- );
133
+ Renderer.act(() => {
134
+ Renderer.updateContainer(
135
+ <a>
136
+ <b />
137
+ </a>,
138
+ container,
139
+ /* parentComponent: */ null,
140
+ /* callback: */ null,
141
+ );
142
+ });
143
});
144
});
packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js
+2
-3
@@ -432,9 +432,8 @@ describe('ReactIncrementalTriangle', () => {
432
assertConsistentTree(activeLeafIndices);
433
}
434
// Flush remaining work
435
- ReactNoop.act(() => {
436
- Scheduler.unstable_flushAllWithoutAsserting();
437
- });
435
+ Scheduler.unstable_flushAllWithoutAsserting();
436
+ ReactNoop.flushSync();
437
assertConsistentTree(activeLeafIndices, expectedCounterAtEnd);
438
}
439