Remove `flushDiscreteUpdates` from end of event (#21223)
We don't need this anymore because we flush in a microtask. This should allow us to remove the logic in the event system that tracks nested event dispatches. I added a test to confirm that nested event dispatches don't triggger a synchronous flush, like they would if we wrapped them `flushSync`. It already passed; I added it to prevent a regression.
Andrew Clark committed
Apr 20, 2021 at 10:25 UTC
78120032d4ca7ee8611d630d7c67e7808885dfe9
7 files changed
+106
-50
packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js
new
+78
@@ -0,0 +1,78 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+describe('ReactDOMNestedEvents', () => {
13
+ let React;
14
+ let ReactDOM;
15
+ let Scheduler;
16
+ let TestUtils;
17
+ let act;
18
+ let useState;
19
+
20
+ beforeEach(() => {
21
+ jest.resetModules();
22
+ React = require('react');
23
+ ReactDOM = require('react-dom');
24
+ Scheduler = require('scheduler');
25
+ TestUtils = require('react-dom/test-utils');
26
+ act = TestUtils.unstable_concurrentAct;
27
+ useState = React.useState;
28
+ });
29
+
30
+ // @gate experimental
31
+ test('nested event dispatches should not cause updates to flush', async () => {
32
+ const buttonRef = React.createRef(null);
33
+ function App() {
34
+ const [isClicked, setIsClicked] = useState(false);
35
+ const [isFocused, setIsFocused] = useState(false);
36
+ const onClick = () => {
37
+ setIsClicked(true);
38
+ const el = buttonRef.current;
39
+ el.focus();
40
+ // The update triggered by the focus event should not have flushed yet.
41
+ // Nor the click update. They would have if we had wrapped the focus
42
+ // call in `flushSync`, though.
43
+ Scheduler.unstable_yieldValue(
44
+ 'Value right after focus call: ' + el.innerHTML,
45
+ );
46
+ };
47
+ const onFocus = () => {
48
+ setIsFocused(true);
49
+ };
50
+ return (
51
+ <>
52
+ <button ref={buttonRef} onFocus={onFocus} onClick={onClick}>
53
+ {`Clicked: ${isClicked}, Focused: ${isFocused}`}
54
+ </button>
55
+ </>
56
+ );
57
+ }
58
+
59
+ const container = document.createElement('div');
60
+ document.body.appendChild(container);
61
+ const root = ReactDOM.unstable_createRoot(container);
62
+
63
+ await act(async () => {
64
+ root.render(<App />);
65
+ });
66
+ expect(buttonRef.current.innerHTML).toEqual(
67
+ 'Clicked: false, Focused: false',
68
+ );
69
+
70
+ await act(async () => {
71
+ buttonRef.current.click();
72
+ });
73
+ expect(Scheduler).toHaveYielded([
74
+ 'Value right after focus call: Clicked: false, Focused: false',
75
+ ]);
76
+ expect(buttonRef.current.innerHTML).toEqual('Clicked: true, Focused: true');
77
+ });
78
+});
packages/react-dom/src/events/ReactDOMEventListener.js
+2
-18
@@ -25,21 +25,13 @@ import {
25
getSuspenseInstanceFromFiber,
26
} from 'react-reconciler/src/ReactFiberTreeReflection';
27
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
28
-import {
29
- type EventSystemFlags,
30
- IS_CAPTURE_PHASE,
31
- IS_LEGACY_FB_SUPPORT_MODE,
32
-} from './EventSystemFlags';
28
+import {type EventSystemFlags, IS_CAPTURE_PHASE} from './EventSystemFlags';
29
30
import getEventTarget from './getEventTarget';
31
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
32
37
-import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags';
33
import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem';
39
-import {
40
- flushDiscreteUpdatesIfNeeded,
41
- discreteUpdates,
42
-} from './ReactDOMUpdateBatching';
34
+import {discreteUpdates} from './ReactDOMUpdateBatching';
35
36
import {
37
getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel,
@@ -120,14 +112,6 @@ function dispatchDiscreteEvent(
112
container,
113
nativeEvent,
114
) {
123
- if (
124
- !enableLegacyFBSupport ||
125
- // If we are in Legacy FB support mode, it means we've already
126
- // flushed for this event and we don't need to do it again.
127
- (eventSystemFlags & IS_LEGACY_FB_SUPPORT_MODE) === 0
128
- ) {
129
- flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
130
- }
115
discreteUpdates(
116
dispatchEvent,
117
domEventName,
packages/react-dom/src/events/ReactDOMUpdateBatching.js
-7
@@ -88,13 +88,6 @@ export function discreteUpdates(fn, a, b, c, d) {
88
}
89
}
90
91
-// TODO: Replace with flushSync
92
-export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
93
- if (!isInsideEventHandler) {
94
- flushDiscreteUpdatesImpl();
95
- }
96
-}
97
-
91
export function setBatchingImplementation(
92
_batchedUpdatesImpl,
93
_discreteUpdatesImpl,
packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+26
-10
@@ -656,7 +656,7 @@ describe('DOMPluginEventSystem', () => {
656
document.body.removeChild(parentContainer);
657
});
658
659
- it('handle click events on dynamic portals', () => {
659
+ it('handle click events on dynamic portals', async () => {
660
const log = [];
661
662
function Parent() {
@@ -670,7 +670,7 @@ describe('DOMPluginEventSystem', () => {
670
ref.current,
671
),
672
);
673
- });
673
+ }, []);
674
675
return (
676
<div ref={ref} onClick={() => log.push('parent')} id="parent">
@@ -679,17 +679,25 @@ describe('DOMPluginEventSystem', () => {
679
);
680
}
681
682
- ReactDOM.render(<Parent />, container);
682
+ await act(async () => {
683
+ ReactDOM.render(<Parent />, container);
684
+ });
685
686
const parent = container.lastChild;
687
expect(parent.id).toEqual('parent');
686
- dispatchClickEvent(parent);
688
+
689
+ await act(async () => {
690
+ dispatchClickEvent(parent);
691
+ });
692
693
expect(log).toEqual(['parent']);
694
695
const child = parent.lastChild;
696
expect(child.id).toEqual('child');
692
- dispatchClickEvent(child);
697
+
698
+ await act(async () => {
699
+ dispatchClickEvent(child);
700
+ });
701
702
// we add both 'child' and 'parent' due to bubbling
703
expect(log).toEqual(['parent', 'child', 'parent']);
@@ -697,7 +705,7 @@ describe('DOMPluginEventSystem', () => {
705
706
// Slight alteration to the last test, to catch
707
// a subtle difference in traversal.
700
- it('handle click events on dynamic portals #2', () => {
708
+ it('handle click events on dynamic portals #2', async () => {
709
const log = [];
710
711
function Parent() {
@@ -711,7 +719,7 @@ describe('DOMPluginEventSystem', () => {
719
ref.current,
720
),
721
);
714
- });
722
+ }, []);
723
724
return (
725
<div ref={ref} onClick={() => log.push('parent')} id="parent">
@@ -720,17 +728,25 @@ describe('DOMPluginEventSystem', () => {
728
);
729
}
730
723
- ReactDOM.render(<Parent />, container);
731
+ await act(async () => {
732
+ ReactDOM.render(<Parent />, container);
733
+ });
734
735
const parent = container.lastChild;
736
expect(parent.id).toEqual('parent');
727
- dispatchClickEvent(parent);
737
+
738
+ await act(async () => {
739
+ dispatchClickEvent(parent);
740
+ });
741
742
expect(log).toEqual(['parent']);
743
744
const child = parent.lastChild;
745
expect(child.id).toEqual('child');
733
- dispatchClickEvent(child);
746
+
747
+ await act(async () => {
748
+ dispatchClickEvent(child);
749
+ });
750
751
// we add both 'child' and 'parent' due to bubbling
752
expect(log).toEqual(['parent', 'child', 'parent']);
packages/react-native-renderer/src/ReactFabric.js
-2
@@ -19,7 +19,6 @@ import {
19
batchedEventUpdates,
20
batchedUpdates as batchedUpdatesImpl,
21
discreteUpdates,
22
- flushDiscreteUpdates,
22
createContainer,
23
updateContainer,
24
injectIntoDevTools,
@@ -242,7 +241,6 @@ function createPortal(
241
setBatchingImplementation(
242
batchedUpdatesImpl,
243
discreteUpdates,
245
- flushDiscreteUpdates,
244
batchedEventUpdates,
245
);
246
packages/react-native-renderer/src/ReactNativeRenderer.js
-2
@@ -19,7 +19,6 @@ import {
19
batchedUpdates as batchedUpdatesImpl,
20
batchedEventUpdates,
21
discreteUpdates,
22
- flushDiscreteUpdates,
22
createContainer,
23
updateContainer,
24
injectIntoDevTools,
@@ -241,7 +240,6 @@ function createPortal(
240
setBatchingImplementation(
241
batchedUpdatesImpl,
242
discreteUpdates,
244
- flushDiscreteUpdates,
243
batchedEventUpdates,
244
);
245
packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js
-11
@@ -18,7 +18,6 @@ let batchedUpdatesImpl = function(fn, bookkeeping) {
18
let discreteUpdatesImpl = function(fn, a, b, c, d) {
19
return fn(a, b, c, d);
20
};
21
-let flushDiscreteUpdatesImpl = function() {};
21
let batchedEventUpdatesImpl = batchedUpdatesImpl;
22
23
let isInsideEventHandler = false;
@@ -59,25 +58,15 @@ export function discreteUpdates(fn, a, b, c, d) {
58
return discreteUpdatesImpl(fn, a, b, c, d);
59
} finally {
60
isInsideEventHandler = prevIsInsideEventHandler;
62
- if (!isInsideEventHandler) {
63
- }
64
- }
65
-}
66
-
67
-export function flushDiscreteUpdatesIfNeeded() {
68
- if (!isInsideEventHandler) {
69
- flushDiscreteUpdatesImpl();
61
}
62
}
63
64
export function setBatchingImplementation(
65
_batchedUpdatesImpl,
66
_discreteUpdatesImpl,
76
- _flushDiscreteUpdatesImpl,
67
_batchedEventUpdatesImpl,
68
) {
69
batchedUpdatesImpl = _batchedUpdatesImpl;
70
discreteUpdatesImpl = _discreteUpdatesImpl;
81
- flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
71
batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
72
}