From 7770d56310f3ee92921568055b5130a5c1a09feb Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Tue, 14 May 2019 12:20:16 +0100 Subject: [PATCH 1/2] flush pending passive effects early on an interactive update related to #15057 --- .../src/__tests__/ReactDOMHooks-test.js | 42 +++++++++++++++++++ .../src/ReactFiberScheduler.js | 3 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js index 360cfa9f9a39..1c3c715b4b95 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js @@ -11,6 +11,7 @@ let React; let ReactDOM; +let act; let Scheduler; describe('ReactDOMHooks', () => { @@ -22,6 +23,7 @@ describe('ReactDOMHooks', () => { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); + act = require('react-dom/test-utils').act; container = document.createElement('div'); document.body.appendChild(container); @@ -179,4 +181,44 @@ describe('ReactDOMHooks', () => { expect(labelRef.current.innerHTML).toBe('abc'); }); + it('should flush passive effects before interactive events', () => { + // related to #15057 + // the test is a bit contrived since it'll never happen in a 'real' test/browser + // (the effect would flush on start and the clicks would never set state) + // but it does model setting state in an effect clean up that removes a previous handler + + // users should probably wrap their code with unstable_interactiveUpdates? or + // use emitEffect? + 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 7864aa0f9a56..95c21c603c21 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -539,6 +539,7 @@ export function flushInteractiveUpdates() { // an input event inside an effect, like with `element.click()`. return; } + flushPassiveEffects(); flushPendingDiscreteUpdates(); } @@ -575,7 +576,7 @@ export function interactiveUpdates( if (workPhase === NotWorking) { // TODO: Remove this call. Instead of doing this automatically, the caller // should explicitly call flushInteractiveUpdates. - flushPendingDiscreteUpdates(); + flushInteractiveUpdates(); } return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c)); } From 1c89f03ab7e12b4df8fdbf00e1bfcd51eadaf625 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Tue, 14 May 2019 12:26:35 +0100 Subject: [PATCH 2/2] stray unused act, prettier --- packages/react-dom/src/__tests__/ReactDOMHooks-test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js index 1c3c715b4b95..68708258c52c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js @@ -11,7 +11,6 @@ let React; let ReactDOM; -let act; let Scheduler; describe('ReactDOMHooks', () => { @@ -23,7 +22,6 @@ describe('ReactDOMHooks', () => { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); - act = require('react-dom/test-utils').act; container = document.createElement('div'); document.body.appendChild(container); @@ -187,8 +185,8 @@ describe('ReactDOMHooks', () => { // (the effect would flush on start and the clicks would never set state) // but it does model setting state in an effect clean up that removes a previous handler - // users should probably wrap their code with unstable_interactiveUpdates? or - // use emitEffect? + // users should probably wrap their code with unstable_interactiveUpdates? or + // use emitEffect? const {useState, useEffect} = React; function Foo() {