diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index 5066cc005a0c6..6f0b981ab95df 100644 --- a/packages/react-dom/src/events/ReactDOMEventListener.js +++ b/packages/react-dom/src/events/ReactDOMEventListener.js @@ -130,7 +130,7 @@ function dispatchDiscreteEvent( // flushed for this event and we don't need to do it again. (eventSystemFlags & IS_LEGACY_FB_SUPPORT_MODE) === 0 ) { - flushDiscreteUpdatesIfNeeded(); + flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp); } discreteUpdates( dispatchEvent, diff --git a/packages/react-dom/src/events/ReactDOMUpdateBatching.js b/packages/react-dom/src/events/ReactDOMUpdateBatching.js index 68d33a8c65a69..f36677e55ef1b 100644 --- a/packages/react-dom/src/events/ReactDOMUpdateBatching.js +++ b/packages/react-dom/src/events/ReactDOMUpdateBatching.js @@ -87,8 +87,27 @@ export function discreteUpdates(fn, a, b, c, d) { } } -export function flushDiscreteUpdatesIfNeeded() { - if (!isInsideEventHandler) { +let lastFlushedEventTimeStamp = 0; +export function flushDiscreteUpdatesIfNeeded(timeStamp: number) { + // event.timeStamp isn't overly reliable due to inconsistencies in + // how different browsers have historically provided the time stamp. + // Some browsers provide high-resolution time stamps for all events, + // some provide low-resolution time stamps for all events. FF < 52 + // even mixes both time stamps together. Some browsers even report + // negative time stamps or time stamps that are 0 (iOS9) in some cases. + // Given we are only comparing two time stamps with equality (!==), + // we are safe from the resolution differences. If the time stamp is 0 + // we bail-out of preventing the flush, which can affect semantics, + // such as if an earlier flush removes or adds event listeners that + // are fired in the subsequent flush. However, this is the same + // behaviour as we had before this change, so the risks are low. + if ( + !isInsideEventHandler && + ( + timeStamp === 0 || + lastFlushedEventTimeStamp !== timeStamp) + ) { + lastFlushedEventTimeStamp = timeStamp; flushDiscreteUpdatesImpl(); } } diff --git a/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js index 0063fa417e7f4..b487a59bc213d 100644 --- a/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js @@ -229,16 +229,12 @@ describe('SimpleEventPlugin', function() { }); describe('interactive events, in concurrent mode', () => { - beforeEach(() => { + // @gate experimental + it('flushes pending interactive work before extracting event handler', () => { jest.resetModules(); - React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); - }); - - // @gate experimental - it('flushes pending interactive work before extracting event handler', () => { container = document.createElement('div'); const root = ReactDOM.unstable_createRoot(container); document.body.appendChild(container);