diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index 7a44cc813656ee..425e28ec0f78f8 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -1031,6 +1031,17 @@ describe('ReactDOMFiber', () => { const handlerA = () => ops.push('A'); const handlerB = () => ops.push('B'); + function click() { + const event = new MouseEvent('click', { + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'timeStamp', { + value: 0, + }); + node.dispatchEvent(event); + } + class Example extends React.Component { state = {flip: false, count: 0}; flip() { @@ -1064,7 +1075,7 @@ describe('ReactDOMFiber', () => { const node = container.firstChild; expect(node.tagName).toEqual('DIV'); - node.click(); + click(); expect(ops).toEqual(['A']); ops = []; diff --git a/packages/react-dom/src/events/ReactDOMEventListener.js b/packages/react-dom/src/events/ReactDOMEventListener.js index 5066cc005a0c64..6f0b981ab95df9 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 68d33a8c65a698..ca0e9c5b2a49c4 100644 --- a/packages/react-dom/src/events/ReactDOMUpdateBatching.js +++ b/packages/react-dom/src/events/ReactDOMUpdateBatching.js @@ -87,8 +87,25 @@ 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 0063fa417e7f41..4ed062404e52f6 100644 --- a/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/ModernSimpleEventPlugin-test.js @@ -275,9 +275,14 @@ describe('SimpleEventPlugin', function() { expect(Scheduler).toFlushAndYield(['render button: enabled']); function click() { - button.dispatchEvent( - new MouseEvent('click', {bubbles: true, cancelable: true}), - ); + const event = new MouseEvent('click', { + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'timeStamp', { + value: 0, + }); + button.dispatchEvent(event); } // Click the button to trigger the side-effect @@ -340,9 +345,14 @@ describe('SimpleEventPlugin', function() { expect(button.textContent).toEqual('Count: 0'); function click() { - button.dispatchEvent( - new MouseEvent('click', {bubbles: true, cancelable: true}), - ); + const event = new MouseEvent('click', { + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'timeStamp', { + value: 0, + }); + button.dispatchEvent(event); } // Click the button a single time @@ -421,9 +431,14 @@ describe('SimpleEventPlugin', function() { expect(button.textContent).toEqual('High-pri count: 0, Low-pri count: 0'); function click() { - button.dispatchEvent( - new MouseEvent('click', {bubbles: true, cancelable: true}), - ); + const event = new MouseEvent('click', { + bubbles: true, + cancelable: true, + }); + Object.defineProperty(event, 'timeStamp', { + value: 0, + }); + button.dispatchEvent(event); } // Click the button a single time