diff --git a/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js b/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js new file mode 100644 index 0000000000000..2de9a3127d331 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMNestedEvents-test.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +describe('ReactDOMNestedEvents', () => { + let React; + let ReactDOM; + let Scheduler; + let TestUtils; + let act; + let useState; + + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOM = require('react-dom'); + Scheduler = require('scheduler'); + TestUtils = require('react-dom/test-utils'); + act = TestUtils.unstable_concurrentAct; + useState = React.useState; + }); + + // @gate experimental + test('nested event dispatches should not cause updates to flush', async () => { + const buttonRef = React.createRef(null); + function App() { + const [isClicked, setIsClicked] = useState(false); + const [isFocused, setIsFocused] = useState(false); + const onClick = () => { + setIsClicked(true); + const el = buttonRef.current; + el.focus(); + // The update triggered by the focus event should not have flushed yet. + // Nor the click update. They would have if we had wrapped the focus + // call in `flushSync`, though. + Scheduler.unstable_yieldValue( + 'Value right after focus call: ' + el.innerHTML, + ); + }; + const onFocus = () => { + setIsFocused(true); + }; + return ( + <> + + + ); + } + + const container = document.createElement('div'); + document.body.appendChild(container); + const root = ReactDOM.unstable_createRoot(container); + + await act(async () => { + root.render(); + }); + expect(buttonRef.current.innerHTML).toEqual( + 'Clicked: false, Focused: false', + ); + + await act(async () => { + buttonRef.current.click(); + }); + expect(Scheduler).toHaveYielded([ + 'Value right after focus call: Clicked: false, Focused: false', + ]); + expect(buttonRef.current.innerHTML).toEqual('Clicked: true, Focused: true'); + }); +}); diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index f09d6ee864481..81b3a1b85dce4 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -25,21 +25,13 @@ import { getSuspenseInstanceFromFiber, } from 'react-reconciler/src/ReactFiberTreeReflection'; import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags'; -import { - type EventSystemFlags, - IS_CAPTURE_PHASE, - IS_LEGACY_FB_SUPPORT_MODE, -} from './EventSystemFlags'; +import {type EventSystemFlags, IS_CAPTURE_PHASE} from './EventSystemFlags'; import getEventTarget from './getEventTarget'; import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree'; -import {enableLegacyFBSupport} from 'shared/ReactFeatureFlags'; import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem'; -import { - flushDiscreteUpdatesIfNeeded, - discreteUpdates, -} from './ReactDOMUpdateBatching'; +import {discreteUpdates} from './ReactDOMUpdateBatching'; import { getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel, @@ -120,14 +112,6 @@ function dispatchDiscreteEvent( container, nativeEvent, ) { - if ( - !enableLegacyFBSupport || - // If we are in Legacy FB support mode, it means we've already - // flushed for this event and we don't need to do it again. - (eventSystemFlags & IS_LEGACY_FB_SUPPORT_MODE) === 0 - ) { - flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp); - } discreteUpdates( dispatchEvent, domEventName, diff --git a/packages/react-dom/src/events/ReactDOMUpdateBatching.js b/packages/react-dom/src/events/ReactDOMUpdateBatching.js index 283afb5b84d63..c768bce0c3a7b 100644 --- a/packages/react-dom/src/events/ReactDOMUpdateBatching.js +++ b/packages/react-dom/src/events/ReactDOMUpdateBatching.js @@ -88,13 +88,6 @@ export function discreteUpdates(fn, a, b, c, d) { } } -// TODO: Replace with flushSync -export function flushDiscreteUpdatesIfNeeded(timeStamp: number) { - if (!isInsideEventHandler) { - flushDiscreteUpdatesImpl(); - } -} - export function setBatchingImplementation( _batchedUpdatesImpl, _discreteUpdatesImpl, diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js index 3491aa7b94f1d..f5b8952a96efa 100644 --- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js @@ -656,7 +656,7 @@ describe('DOMPluginEventSystem', () => { document.body.removeChild(parentContainer); }); - it('handle click events on dynamic portals', () => { + it('handle click events on dynamic portals', async () => { const log = []; function Parent() { @@ -670,7 +670,7 @@ describe('DOMPluginEventSystem', () => { ref.current, ), ); - }); + }, []); return (
log.push('parent')} id="parent"> @@ -679,17 +679,25 @@ describe('DOMPluginEventSystem', () => { ); } - ReactDOM.render(, container); + await act(async () => { + ReactDOM.render(, container); + }); const parent = container.lastChild; expect(parent.id).toEqual('parent'); - dispatchClickEvent(parent); + + await act(async () => { + dispatchClickEvent(parent); + }); expect(log).toEqual(['parent']); const child = parent.lastChild; expect(child.id).toEqual('child'); - dispatchClickEvent(child); + + await act(async () => { + dispatchClickEvent(child); + }); // we add both 'child' and 'parent' due to bubbling expect(log).toEqual(['parent', 'child', 'parent']); @@ -697,7 +705,7 @@ describe('DOMPluginEventSystem', () => { // Slight alteration to the last test, to catch // a subtle difference in traversal. - it('handle click events on dynamic portals #2', () => { + it('handle click events on dynamic portals #2', async () => { const log = []; function Parent() { @@ -711,7 +719,7 @@ describe('DOMPluginEventSystem', () => { ref.current, ), ); - }); + }, []); return (
log.push('parent')} id="parent"> @@ -720,17 +728,25 @@ describe('DOMPluginEventSystem', () => { ); } - ReactDOM.render(, container); + await act(async () => { + ReactDOM.render(, container); + }); const parent = container.lastChild; expect(parent.id).toEqual('parent'); - dispatchClickEvent(parent); + + await act(async () => { + dispatchClickEvent(parent); + }); expect(log).toEqual(['parent']); const child = parent.lastChild; expect(child.id).toEqual('child'); - dispatchClickEvent(child); + + await act(async () => { + dispatchClickEvent(child); + }); // we add both 'child' and 'parent' due to bubbling expect(log).toEqual(['parent', 'child', 'parent']); diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index 5f797c0dcfa39..9f1f44f51cf49 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -19,7 +19,6 @@ import { batchedEventUpdates, batchedUpdates as batchedUpdatesImpl, discreteUpdates, - flushDiscreteUpdates, createContainer, updateContainer, injectIntoDevTools, @@ -242,7 +241,6 @@ function createPortal( setBatchingImplementation( batchedUpdatesImpl, discreteUpdates, - flushDiscreteUpdates, batchedEventUpdates, ); diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index 1e7b390f9ff38..de42c6e41a58c 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -19,7 +19,6 @@ import { batchedUpdates as batchedUpdatesImpl, batchedEventUpdates, discreteUpdates, - flushDiscreteUpdates, createContainer, updateContainer, injectIntoDevTools, @@ -241,7 +240,6 @@ function createPortal( setBatchingImplementation( batchedUpdatesImpl, discreteUpdates, - flushDiscreteUpdates, batchedEventUpdates, ); diff --git a/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js b/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js index 89bbdd203b14d..3b30bb274ff1b 100644 --- a/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js +++ b/packages/react-native-renderer/src/legacy-events/ReactGenericBatching.js @@ -18,7 +18,6 @@ let batchedUpdatesImpl = function(fn, bookkeeping) { let discreteUpdatesImpl = function(fn, a, b, c, d) { return fn(a, b, c, d); }; -let flushDiscreteUpdatesImpl = function() {}; let batchedEventUpdatesImpl = batchedUpdatesImpl; let isInsideEventHandler = false; @@ -59,25 +58,15 @@ export function discreteUpdates(fn, a, b, c, d) { return discreteUpdatesImpl(fn, a, b, c, d); } finally { isInsideEventHandler = prevIsInsideEventHandler; - if (!isInsideEventHandler) { - } - } -} - -export function flushDiscreteUpdatesIfNeeded() { - if (!isInsideEventHandler) { - flushDiscreteUpdatesImpl(); } } export function setBatchingImplementation( _batchedUpdatesImpl, _discreteUpdatesImpl, - _flushDiscreteUpdatesImpl, _batchedEventUpdatesImpl, ) { batchedUpdatesImpl = _batchedUpdatesImpl; discreteUpdatesImpl = _discreteUpdatesImpl; - flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl; batchedEventUpdatesImpl = _batchedEventUpdatesImpl; }