diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
index 5dff31ad17a60..0502555707c37 100644
--- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js
@@ -179,4 +179,36 @@ describe('ReactDOMHooks', () => {
expect(labelRef.current.innerHTML).toBe('abc');
});
+
+ it('should flush passive effects before interactive events', () => {
+ const {useState, useEffect} = React;
+
+ function Foo() {
+ const [count, setCount] = useState(0);
+ const [enabled, setEnabled] = useState(true);
+ useEffect(() => {
+ return () => {
+ setEnabled(false);
+ };
+ });
+ function handleClick() {
+ setCount(x => x + 1);
+ }
+ return ;
+ }
+
+ ReactDOM.render(, container);
+ container.firstChild.dispatchEvent(
+ new Event('click', {bubbles: true, cancelable: true}),
+ );
+ // Cleanup from first passive effect should remove the handler.
+ container.firstChild.dispatchEvent(
+ new Event('click', {bubbles: true, cancelable: true}),
+ );
+ container.firstChild.dispatchEvent(
+ new Event('click', {bubbles: true, cancelable: true}),
+ );
+ jest.runAllTimers();
+ expect(container.textContent).toBe('1');
+ });
});
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index e24e2ad714803..4bdcabf6ae01c 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -2541,14 +2541,8 @@ function interactiveUpdates(
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
- if (
- !isBatchingUpdates &&
- !isRendering &&
- lowestPriorityPendingInteractiveExpirationTime !== NoWork
- ) {
- // Synchronously flush pending interactive updates.
- performWork(lowestPriorityPendingInteractiveExpirationTime, false);
- lowestPriorityPendingInteractiveExpirationTime = NoWork;
+ if (!isBatchingUpdates) {
+ flushInteractiveUpdates();
}
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
@@ -2571,6 +2565,7 @@ function flushInteractiveUpdates() {
lowestPriorityPendingInteractiveExpirationTime !== NoWork
) {
// Synchronously flush pending interactive updates.
+ flushPassiveEffects();
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}