diff --git a/fixtures/dom/.gitignore b/fixtures/dom/.gitignore
index 9f05c1cc2b73d..724d1459422c9 100644
--- a/fixtures/dom/.gitignore
+++ b/fixtures/dom/.gitignore
@@ -14,6 +14,8 @@ public/react-dom.development.js
 public/react-dom.production.min.js
 public/react-dom-server.browser.development.js
 public/react-dom-server.browser.production.min.js
+public/react-dom-test-utils.development.js
+public/react-dom-test-utils.production.min.js
 
 # misc
 .DS_Store
diff --git a/fixtures/dom/package.json b/fixtures/dom/package.json
index 940b66736ce4e..f5f84257f7c82 100644
--- a/fixtures/dom/package.json
+++ b/fixtures/dom/package.json
@@ -18,7 +18,7 @@
   },
   "scripts": {
     "start": "react-scripts start",
-    "prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/",
+    "prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.development.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.production.min.js public/",
     "build": "react-scripts build && cp build/index.html build/200.html",
     "test": "react-scripts test --env=jsdom",
     "eject": "react-scripts eject"
diff --git a/fixtures/dom/public/act-dom.html b/fixtures/dom/public/act-dom.html
new file mode 100644
index 0000000000000..2fb4a437721df
--- /dev/null
+++ b/fixtures/dom/public/act-dom.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>sanity test for ReactTestUtils.act</title>
+</head>
+<body>
+  this page tests whether act runs properly in a browser.
+  <br/>
+  your console should say "5"
+  <script src='react.development.js'></script>
+  <script src='react-dom.development.js'></script>
+  <script src='react-dom-test-utils.development.js'></script>
+  <script>
+    async function run(){
+      // from ReactTestUtilsAct-test.js
+      function App() {
+        let [state, setState] = React.useState(0);
+        async function ticker() {
+          await null;
+          setState(x => x + 1);
+        }
+        React.useEffect(
+          () => {
+            ticker();
+          },
+          [Math.min(state, 4)],
+        );
+        return state;
+      }
+      const el = document.createElement('div');
+      await ReactTestUtils.act(async () => {
+        ReactDOM.render(React.createElement(App), el);
+      });
+      // all 5 ticks present and accounted for
+      console.log(el.innerHTML);
+    }
+    run();
+    
+  </script>
+</body>
+</html>
diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
index 3e35c28dbc20d..687dbd1aaec2c 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
@@ -14,7 +14,6 @@ let React;
 let ReactDOM;
 let ReactDOMServer;
 let ReactTestUtils;
-let act;
 
 function getTestDocument(markup) {
   const doc = document.implementation.createHTMLDocument('');
@@ -34,7 +33,6 @@ describe('ReactTestUtils', () => {
     ReactDOM = require('react-dom');
     ReactDOMServer = require('react-dom/server');
     ReactTestUtils = require('react-dom/test-utils');
-    act = ReactTestUtils.act;
   });
 
   it('Simulate should have locally attached media events', () => {
@@ -517,173 +515,4 @@ describe('ReactTestUtils', () => {
     ReactTestUtils.renderIntoDocument(<Component />);
     expect(mockArgs.length).toEqual(0);
   });
-
-  it('can use act to batch effects', () => {
-    function App(props) {
-      React.useEffect(props.callback);
-      return null;
-    }
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-
-    try {
-      let called = false;
-      act(() => {
-        ReactDOM.render(
-          <App
-            callback={() => {
-              called = true;
-            }}
-          />,
-          container,
-        );
-      });
-
-      expect(called).toBe(true);
-    } finally {
-      document.body.removeChild(container);
-    }
-  });
-
-  it('flushes effects on every call', () => {
-    function App(props) {
-      let [ctr, setCtr] = React.useState(0);
-      React.useEffect(() => {
-        props.callback(ctr);
-      });
-      return (
-        <button id="button" onClick={() => setCtr(x => x + 1)}>
-          click me!
-        </button>
-      );
-    }
-
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-    let calledCtr = 0;
-    act(() => {
-      ReactDOM.render(
-        <App
-          callback={val => {
-            calledCtr = val;
-          }}
-        />,
-        container,
-      );
-    });
-    const button = document.getElementById('button');
-    function click() {
-      button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
-    }
-
-    act(() => {
-      click();
-      click();
-      click();
-    });
-    expect(calledCtr).toBe(3);
-    act(click);
-    expect(calledCtr).toBe(4);
-    act(click);
-    expect(calledCtr).toBe(5);
-
-    document.body.removeChild(container);
-  });
-
-  it('can use act to batch effects on updates too', () => {
-    function App() {
-      let [ctr, setCtr] = React.useState(0);
-      return (
-        <button id="button" onClick={() => setCtr(x => x + 1)}>
-          {ctr}
-        </button>
-      );
-    }
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-    let button;
-    act(() => {
-      ReactDOM.render(<App />, container);
-    });
-    button = document.getElementById('button');
-    expect(button.innerHTML).toBe('0');
-    act(() => {
-      button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
-    });
-    expect(button.innerHTML).toBe('1');
-    document.body.removeChild(container);
-  });
-
-  it('detects setState being called outside of act(...)', () => {
-    let setValueRef = null;
-    function App() {
-      let [value, setValue] = React.useState(0);
-      setValueRef = setValue;
-      return (
-        <button id="button" onClick={() => setValue(2)}>
-          {value}
-        </button>
-      );
-    }
-    const container = document.createElement('div');
-    document.body.appendChild(container);
-    let button;
-    act(() => {
-      ReactDOM.render(<App />, container);
-      button = container.querySelector('#button');
-      button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
-    });
-    expect(button.innerHTML).toBe('2');
-    expect(() => setValueRef(1)).toWarnDev([
-      'An update to App inside a test was not wrapped in act(...).',
-    ]);
-    document.body.removeChild(container);
-  });
-
-  it('lets a ticker update', () => {
-    function App() {
-      let [toggle, setToggle] = React.useState(0);
-      React.useEffect(() => {
-        let timeout = setTimeout(() => {
-          setToggle(1);
-        }, 200);
-        return () => clearTimeout(timeout);
-      });
-      return toggle;
-    }
-    const container = document.createElement('div');
-
-    act(() => {
-      act(() => {
-        ReactDOM.render(<App />, container);
-      });
-      jest.advanceTimersByTime(250);
-    });
-
-    expect(container.innerHTML).toBe('1');
-  });
-
-  it('warns if you return a value inside act', () => {
-    expect(() => act(() => null)).toWarnDev(
-      [
-        'The callback passed to ReactTestUtils.act(...) function must not return anything.',
-      ],
-      {withoutStack: true},
-    );
-    expect(() => act(() => 123)).toWarnDev(
-      [
-        'The callback passed to ReactTestUtils.act(...) function must not return anything.',
-      ],
-      {withoutStack: true},
-    );
-  });
-
-  it('warns if you try to await an .act call', () => {
-    expect(act(() => {}).then).toWarnDev(
-      [
-        'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
-      ],
-      {withoutStack: true},
-    );
-  });
 });
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
new file mode 100644
index 0000000000000..6e037088c9204
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
@@ -0,0 +1,403 @@
+/**
+ * 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
+ */
+
+let React;
+let ReactDOM;
+let ReactTestUtils;
+let act;
+
+jest.useRealTimers();
+
+function sleep(period) {
+  return new Promise(resolve => {
+    setTimeout(() => {
+      resolve(true);
+    }, period);
+  });
+}
+
+describe('ReactTestUtils.act()', () => {
+  beforeEach(() => {
+    jest.resetModules();
+    React = require('react');
+    ReactDOM = require('react-dom');
+    ReactTestUtils = require('react-dom/test-utils');
+    act = ReactTestUtils.act;
+  });
+
+  describe('sync', () => {
+    it('can use act to flush effects', () => {
+      function App(props) {
+        React.useEffect(props.callback);
+        return null;
+      }
+
+      let calledLog = [];
+      act(() => {
+        ReactDOM.render(
+          <App
+            callback={() => {
+              calledLog.push(calledLog.length);
+            }}
+          />,
+          document.createElement('div'),
+        );
+      });
+
+      expect(calledLog).toEqual([0]);
+    });
+
+    it('flushes effects on every call', () => {
+      function App(props) {
+        let [ctr, setCtr] = React.useState(0);
+        React.useEffect(() => {
+          props.callback(ctr);
+        });
+        return (
+          <button id="button" onClick={() => setCtr(x => x + 1)}>
+            {ctr}
+          </button>
+        );
+      }
+
+      const container = document.createElement('div');
+      // attach to body so events works
+      document.body.appendChild(container);
+      let calledCounter = 0;
+      act(() => {
+        ReactDOM.render(
+          <App
+            callback={val => {
+              calledCounter = val;
+            }}
+          />,
+          container,
+        );
+      });
+      const button = document.getElementById('button');
+      function click() {
+        button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
+      }
+
+      act(() => {
+        click();
+        click();
+        click();
+      });
+      expect(calledCounter).toBe(3);
+      act(click);
+      expect(calledCounter).toBe(4);
+      act(click);
+      expect(calledCounter).toBe(5);
+      expect(button.innerHTML).toBe('5');
+
+      document.body.removeChild(container);
+    });
+
+    it('should flush effects recursively', () => {
+      function App() {
+        let [ctr, setCtr] = React.useState(0);
+        React.useEffect(() => {
+          if (ctr < 5) {
+            setCtr(x => x + 1);
+          }
+        });
+        return ctr;
+      }
+
+      const container = document.createElement('div');
+      act(() => {
+        ReactDOM.render(<App />, container);
+      });
+
+      expect(container.innerHTML).toBe('5');
+    });
+
+    it('detects setState being called outside of act(...)', () => {
+      let setValue = null;
+      function App() {
+        let [value, _setValue] = React.useState(0);
+        setValue = _setValue;
+        return (
+          <button id="button" onClick={() => setValue(2)}>
+            {value}
+          </button>
+        );
+      }
+      const container = document.createElement('div');
+      document.body.appendChild(container);
+      let button;
+      act(() => {
+        ReactDOM.render(<App />, container);
+        button = container.querySelector('#button');
+        button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
+      });
+      expect(button.innerHTML).toBe('2');
+      expect(() => setValue(1)).toWarnDev([
+        'An update to App inside a test was not wrapped in act(...).',
+      ]);
+      document.body.removeChild(container);
+    });
+    describe('fake timers', () => {
+      beforeEach(() => {
+        jest.useFakeTimers();
+      });
+      afterEach(() => {
+        jest.useRealTimers();
+      });
+      it('lets a ticker update', () => {
+        function App() {
+          let [toggle, setToggle] = React.useState(0);
+          React.useEffect(() => {
+            let timeout = setTimeout(() => {
+              setToggle(1);
+            }, 200);
+            return () => clearTimeout(timeout);
+          }, []);
+          return toggle;
+        }
+        const container = document.createElement('div');
+
+        act(() => {
+          ReactDOM.render(<App />, container);
+        });
+        act(() => {
+          jest.runAllTimers();
+        });
+
+        expect(container.innerHTML).toBe('1');
+      });
+      it('can use the async version to catch microtasks', async () => {
+        function App() {
+          let [toggle, setToggle] = React.useState(0);
+          React.useEffect(() => {
+            // just like the previous test, except we
+            // use a promise and schedule the update
+            // after it resolves
+            sleep(200).then(() => setToggle(1));
+          }, []);
+          return toggle;
+        }
+        const container = document.createElement('div');
+
+        act(() => {
+          ReactDOM.render(<App />, container);
+        });
+        await act(async () => {
+          jest.runAllTimers();
+        });
+
+        expect(container.innerHTML).toBe('1');
+      });
+      it('can handle cascading promises with fake timers', async () => {
+        // this component triggers an effect, that waits a tick,
+        // then sets state. repeats this 5 times.
+        function App() {
+          let [state, setState] = React.useState(0);
+          async function ticker() {
+            await null;
+            setState(x => x + 1);
+          }
+          React.useEffect(
+            () => {
+              ticker();
+            },
+            [Math.min(state, 4)],
+          );
+          return state;
+        }
+        const el = document.createElement('div');
+        await act(async () => {
+          ReactDOM.render(<App />, el);
+        });
+
+        // all 5 ticks present and accounted for
+        expect(el.innerHTML).toBe('5');
+      });
+    });
+
+    it('warns if you return a value inside act', () => {
+      expect(() => act(() => null)).toWarnDev(
+        [
+          'The callback passed to act(...) function must return undefined, or a Promise.',
+        ],
+        {withoutStack: true},
+      );
+      expect(() => act(() => 123)).toWarnDev(
+        [
+          'The callback passed to act(...) function must return undefined, or a Promise.',
+        ],
+        {withoutStack: true},
+      );
+    });
+
+    it('warns if you try to await an .act call', () => {
+      expect(() => act(() => {}).then(() => {})).toWarnDev(
+        [
+          'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+        ],
+        {withoutStack: true},
+      );
+    });
+  });
+  describe('asynchronous tests', () => {
+    it('can handle timers', async () => {
+      function App() {
+        let [ctr, setCtr] = React.useState(0);
+        function doSomething() {
+          setTimeout(() => {
+            setCtr(1);
+          }, 50);
+        }
+
+        React.useEffect(() => {
+          doSomething();
+        }, []);
+        return ctr;
+      }
+      const el = document.createElement('div');
+      await act(async () => {
+        act(() => {
+          ReactDOM.render(<App />, el);
+        });
+
+        await sleep(100);
+        expect(el.innerHTML).toBe('1');
+      });
+    });
+
+    it('can handle async/await', async () => {
+      function App() {
+        let [ctr, setCtr] = React.useState(0);
+        async function someAsyncFunction() {
+          // queue a bunch of promises to be sure they all flush
+          await null;
+          await null;
+          await null;
+          setCtr(1);
+        }
+        React.useEffect(() => {
+          someAsyncFunction();
+        }, []);
+        return ctr;
+      }
+      const el = document.createElement('div');
+
+      await act(async () => {
+        act(() => {
+          ReactDOM.render(<App />, el);
+        });
+        // pending promises will close before this ends
+      });
+      expect(el.innerHTML).toEqual('1');
+    });
+
+    it('warns if you do not await an act call', async () => {
+      spyOnDevAndProd(console, 'error');
+      act(async () => {});
+      // it's annoying that we have to wait a tick before this warning comes in
+      await sleep(0);
+      if (__DEV__) {
+        expect(console.error.calls.count()).toEqual(1);
+        expect(console.error.calls.argsFor(0)[0]).toMatch(
+          'You called act(async () => ...) without await.',
+        );
+      }
+    });
+
+    it('warns if you try to interleave multiple act calls', async () => {
+      spyOnDevAndProd(console, 'error');
+      // let's try to cheat and spin off a 'thread' with an act call
+      (async () => {
+        await act(async () => {
+          await sleep(50);
+        });
+      })();
+
+      await act(async () => {
+        await sleep(100);
+      });
+
+      await sleep(150);
+      if (__DEV__) {
+        expect(console.error).toHaveBeenCalledTimes(1);
+      }
+    });
+
+    it('commits and effects are guaranteed to be flushed', async () => {
+      function App(props) {
+        let [state, setState] = React.useState(0);
+        async function something() {
+          await null;
+          setState(1);
+        }
+        React.useEffect(() => {
+          something();
+        }, []);
+        React.useEffect(() => {
+          props.callback();
+        });
+        return state;
+      }
+      let ctr = 0;
+      const div = document.createElement('div');
+
+      await act(async () => {
+        act(() => {
+          ReactDOM.render(<App callback={() => ctr++} />, div);
+        });
+        expect(div.innerHTML).toBe('0');
+        expect(ctr).toBe(1);
+      });
+      // this may seem odd, but it matches user behaviour -
+      // a flash of "0" followed by "1"
+
+      expect(div.innerHTML).toBe('1');
+      expect(ctr).toBe(2);
+    });
+
+    it('propagates errors', async () => {
+      let err;
+      try {
+        await act(async () => {
+          throw new Error('some error');
+        });
+      } catch (_err) {
+        err = _err;
+      } finally {
+        expect(err instanceof Error).toBe(true);
+        expect(err.message).toBe('some error');
+      }
+    });
+    it('can handle cascading promises', async () => {
+      // this component triggers an effect, that waits a tick,
+      // then sets state. repeats this 5 times.
+      function App() {
+        let [state, setState] = React.useState(0);
+        async function ticker() {
+          await null;
+          setState(x => x + 1);
+        }
+        React.useEffect(
+          () => {
+            ticker();
+          },
+          [Math.min(state, 4)],
+        );
+        return state;
+      }
+      const el = document.createElement('div');
+      await act(async () => {
+        ReactDOM.render(<App />, el);
+      });
+      // all 5 ticks present and accounted for
+      expect(el.innerHTML).toBe('5');
+    });
+  });
+});
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index e226843322f78..728f775adf5e2 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -35,6 +35,7 @@ import {
   getPublicRootInstance,
   findHostInstance,
   findHostInstanceWithWarning,
+  flushPassiveEffects,
 } from 'react-reconciler/inline.dom';
 import {createPortal as createPortalImpl} from 'shared/ReactPortal';
 import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -807,7 +808,7 @@ const ReactDOM: Object = {
 
   __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
     // Keep in sync with ReactDOMUnstableNativeDependencies.js
-    // and ReactTestUtils.js. This is an array for better minification.
+    // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.
     Events: [
       getInstanceFromNode,
       getNodeFromInstance,
@@ -820,6 +821,7 @@ const ReactDOM: Object = {
       restoreStateIfNeeded,
       dispatchEvent,
       runEventsInBatch,
+      flushPassiveEffects,
     ],
   },
 };
diff --git a/packages/react-dom/src/fire/ReactFire.js b/packages/react-dom/src/fire/ReactFire.js
index 525f935882254..e4f555ebd627d 100644
--- a/packages/react-dom/src/fire/ReactFire.js
+++ b/packages/react-dom/src/fire/ReactFire.js
@@ -40,6 +40,7 @@ import {
   getPublicRootInstance,
   findHostInstance,
   findHostInstanceWithWarning,
+  flushPassiveEffects,
 } from 'react-reconciler/inline.fire';
 import {createPortal as createPortalImpl} from 'shared/ReactPortal';
 import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -826,6 +827,7 @@ const ReactDOM: Object = {
       restoreStateIfNeeded,
       dispatchEvent,
       runEventsInBatch,
+      flushPassiveEffects,
     ],
   },
 };
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index 06c39357fb29e..f463cc4aa6a32 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -4,6 +4,7 @@
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
 
 import React from 'react';
 import ReactDOM from 'react-dom';
@@ -22,15 +23,11 @@ import warningWithoutStack from 'shared/warningWithoutStack';
 import {ELEMENT_NODE} from '../shared/HTMLNodeType';
 import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
 import {PLUGIN_EVENT_SYSTEM} from 'events/EventSystemFlags';
-
-// for .act's return value
-type Thenable = {
-  then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
+import act from './ReactTestUtilsAct';
 
 const {findDOMNode} = ReactDOM;
 // Keep in sync with ReactDOMUnstableNativeDependencies.js
-// and ReactDOM.js:
+// ReactDOM.js, and ReactTestUtilsAct.js:
 const [
   getInstanceFromNode,
   /* eslint-disable no-unused-vars */
@@ -45,6 +42,8 @@ const [
   restoreStateIfNeeded,
   dispatchEvent,
   runEventsInBatch,
+  // eslint-disable-next-line no-unused-vars
+  flushPassiveEffects,
 ] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
 
 function Event(suffix) {}
@@ -152,9 +151,12 @@ function validateClassInstance(inst, methodName) {
   );
 }
 
-// a stub element, lazily initialized, used by act() when flushing effects
+// a plain dom element, lazily initialized, used by act() when flushing effects
 let actContainerElement = null;
 
+// a warning for when you try to use TestUtils.act in a non-browser environment
+let didWarnAboutActInNodejs = false;
+
 /**
  * Utilities for making it easy to test React components.
  *
@@ -391,57 +393,24 @@ const ReactTestUtils = {
   Simulate: null,
   SimulateNative: {},
 
-  act(callback: () => void): Thenable {
+  act(callback: () => Thenable) {
     if (actContainerElement === null) {
-      // warn if we can't actually create the stub element
       if (__DEV__) {
-        warningWithoutStack(
-          typeof document !== 'undefined' &&
-            document !== null &&
-            typeof document.createElement === 'function',
-          'It looks like you called TestUtils.act(...) in a non-browser environment. ' +
-            "If you're using TestRenderer for your tests, you should call " +
-            'TestRenderer.act(...) instead of TestUtils.act(...).',
-        );
-      }
-      // then make it
-      actContainerElement = document.createElement('div');
-    }
-
-    const result = ReactDOM.unstable_batchedUpdates(callback);
-    // note: keep these warning messages in sync with
-    // createReactNoop.js and ReactTestRenderer.js
-    if (__DEV__) {
-      if (result !== undefined) {
-        let addendum;
-        if (result !== null && typeof result.then === 'function') {
-          addendum =
-            '\n\nIt looks like you wrote ReactTestUtils.act(async () => ...), ' +
-            'or returned a Promise from the callback passed to it. ' +
-            'Putting asynchronous logic inside ReactTestUtils.act(...) is not supported.\n';
-        } else {
-          addendum = ' You returned: ' + result;
-        }
-        warningWithoutStack(
-          false,
-          'The callback passed to ReactTestUtils.act(...) function must not return anything.%s',
-          addendum,
-        );
-      }
-    }
-    ReactDOM.render(<div />, actContainerElement);
-    // we want the user to not expect a return,
-    // but we want to warn if they use it like they can await on it.
-    return {
-      then() {
-        if (__DEV__) {
+        // warn if we're trying to use this in something like node (without jsdom)
+        if (didWarnAboutActInNodejs === false) {
+          didWarnAboutActInNodejs = true;
           warningWithoutStack(
-            false,
-            'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
+            typeof document !== 'undefined' && document !== null,
+            'It looks like you called ReactTestUtils.act(...) in a non-browser environment. ' +
+              "If you're using TestRenderer for your tests, you should call " +
+              'ReactTestRenderer.act(...) instead of ReactTestUtils.act(...).',
           );
         }
-      },
-    };
+      }
+      // now make the stub element
+      actContainerElement = document.createElement('div');
+    }
+    return act(callback);
   },
 };
 
diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
new file mode 100644
index 0000000000000..99cb73ede7c10
--- /dev/null
+++ b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
@@ -0,0 +1,172 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
+
+import warningWithoutStack from 'shared/warningWithoutStack';
+import ReactDOM from 'react-dom';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import enqueueTask from 'shared/enqueueTask';
+
+// Keep in sync with ReactDOMUnstableNativeDependencies.js
+// ReactDOM.js, and ReactTestUtils.js:
+const [
+  /* eslint-disable no-unused-vars */
+  getInstanceFromNode,
+  getNodeFromInstance,
+  getFiberCurrentPropsFromNode,
+  injectEventPluginsByName,
+  eventNameDispatchConfigs,
+  accumulateTwoPhaseDispatches,
+  accumulateDirectDispatches,
+  enqueueStateRestore,
+  restoreStateIfNeeded,
+  dispatchEvent,
+  runEventsInBatch,
+  /* eslint-enable no-unused-vars */
+  flushPassiveEffects,
+] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
+
+const batchedUpdates = ReactDOM.unstable_batchedUpdates;
+
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
+// this implementation should be exactly the same in
+// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+// we track the 'depth' of the act() calls with this counter,
+// so we can tell if any async act() calls try to run in parallel.
+let actingUpdatesScopeDepth = 0;
+
+function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+  try {
+    flushPassiveEffects();
+    enqueueTask(() => {
+      if (flushPassiveEffects()) {
+        flushEffectsAndMicroTasks(onDone);
+      } else {
+        onDone();
+      }
+    });
+  } catch (err) {
+    onDone(err);
+  }
+}
+
+function act(callback: () => Thenable) {
+  let previousActingUpdatesScopeDepth;
+  if (__DEV__) {
+    previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+    actingUpdatesScopeDepth++;
+    ReactShouldWarnActingUpdates.current = true;
+  }
+
+  function onDone() {
+    if (__DEV__) {
+      actingUpdatesScopeDepth--;
+      if (actingUpdatesScopeDepth === 0) {
+        ReactShouldWarnActingUpdates.current = false;
+      }
+      if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+        // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+        warningWithoutStack(
+          null,
+          'You seem to have overlapping act() calls, this is not supported. ' +
+            'Be sure to await previous act() calls before making a new one. ',
+        );
+      }
+    }
+  }
+
+  const result = batchedUpdates(callback);
+  if (
+    result !== null &&
+    typeof result === 'object' &&
+    typeof result.then === 'function'
+  ) {
+    // setup a boolean that gets set to true only
+    // once this act() call is await-ed
+    let called = false;
+    if (__DEV__) {
+      if (typeof Promise !== 'undefined') {
+        //eslint-disable-next-line no-undef
+        Promise.resolve()
+          .then(() => {})
+          .then(() => {
+            if (called === false) {
+              warningWithoutStack(
+                null,
+                'You called act(async () => ...) without await. ' +
+                  'This could lead to unexpected testing behaviour, interleaving multiple act ' +
+                  'calls and mixing their scopes. You should - await act(async () => ...);',
+              );
+            }
+          });
+      }
+    }
+
+    // in the async case, the returned thenable runs the callback, flushes
+    // effects and  microtasks in a loop until flushPassiveEffects() === false,
+    // and cleans up
+    return {
+      then(resolve: () => void, reject: (?Error) => void) {
+        called = true;
+        result.then(
+          () => {
+            flushEffectsAndMicroTasks((err: ?Error) => {
+              onDone();
+              if (err) {
+                reject(err);
+              } else {
+                resolve();
+              }
+            });
+          },
+          err => {
+            onDone();
+            reject(err);
+          },
+        );
+      },
+    };
+  } else {
+    if (__DEV__) {
+      warningWithoutStack(
+        result === undefined,
+        'The callback passed to act(...) function ' +
+          'must return undefined, or a Promise. You returned %s',
+        result,
+      );
+    }
+
+    // flush effects until none remain, and cleanup
+    try {
+      while (flushPassiveEffects()) {}
+      onDone();
+    } catch (err) {
+      onDone();
+      throw err;
+    }
+
+    // in the sync case, the returned thenable only warns *if* await-ed
+    return {
+      then(resolve: () => void) {
+        if (__DEV__) {
+          warningWithoutStack(
+            false,
+            'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+          );
+        }
+        resolve();
+      },
+    };
+  }
+}
+
+export default act;
diff --git a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
index f382613ccce72..d53daa4eda107 100644
--- a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
+++ b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
@@ -11,7 +11,7 @@ import ResponderEventPlugin from 'events/ResponderEventPlugin';
 import ResponderTouchHistoryStore from 'events/ResponderTouchHistoryStore';
 
 // Inject react-dom's ComponentTree into this module.
-// Keep in sync with ReactDOM.js and ReactTestUtils.js:
+// Keep in sync with ReactDOM.js, ReactTestUtils.js, and ReactTestUtilsAct.js:
 const [
   getInstanceFromNode,
   getNodeFromInstance,
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 8976372bf6c48..55198b6ba43ad 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -14,6 +14,7 @@
  * environment.
  */
 
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
 import type {Fiber} from 'react-reconciler/src/ReactFiber';
 import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
 import type {ReactNodeList} from 'shared/ReactTypes';
@@ -26,16 +27,12 @@ import {
   REACT_ELEMENT_TYPE,
   REACT_EVENT_TARGET_TOUCH_HIT,
 } from 'shared/ReactSymbols';
-import warningWithoutStack from 'shared/warningWithoutStack';
 import warning from 'shared/warning';
-
+import enqueueTask from 'shared/enqueueTask';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import warningWithoutStack from 'shared/warningWithoutStack';
 import {enableEventAPI} from 'shared/ReactFeatureFlags';
 
-// for .act's return value
-type Thenable = {
-  then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
-
 type Container = {
   rootID: string,
   children: Array<Instance | TextInstance>,
@@ -59,6 +56,8 @@ type TextInstance = {|
 |};
 type HostContext = Object;
 
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
 const NO_CONTEXT = {};
 const UPPERCASE_CONTEXT = {};
 const EVENT_COMPONENT_CONTEXT = {};
@@ -598,6 +597,140 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
   const roots = new Map();
   const DEFAULT_ROOT_ID = '<default>';
 
+  const {flushPassiveEffects, batchedUpdates} = NoopRenderer;
+
+  // this act() implementation should be exactly the same in
+  // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+  let actingUpdatesScopeDepth = 0;
+
+  function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+    try {
+      flushPassiveEffects();
+      enqueueTask(() => {
+        if (flushPassiveEffects()) {
+          flushEffectsAndMicroTasks(onDone);
+        } else {
+          onDone();
+        }
+      });
+    } catch (err) {
+      onDone(err);
+    }
+  }
+
+  function act(callback: () => Thenable) {
+    let previousActingUpdatesScopeDepth;
+    if (__DEV__) {
+      previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+      actingUpdatesScopeDepth++;
+      ReactShouldWarnActingUpdates.current = true;
+    }
+
+    function onDone() {
+      if (__DEV__) {
+        actingUpdatesScopeDepth--;
+        if (actingUpdatesScopeDepth === 0) {
+          ReactShouldWarnActingUpdates.current = false;
+        }
+        if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+          // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+          warningWithoutStack(
+            null,
+            'You seem to have overlapping act() calls, this is not supported. ' +
+              'Be sure to await previous act() calls before making a new one. ',
+          );
+        }
+      }
+    }
+
+    const result = batchedUpdates(callback);
+    if (
+      result !== null &&
+      typeof result === 'object' &&
+      typeof result.then === 'function'
+    ) {
+      // setup a boolean that gets set to true only
+      // once this act() call is await-ed
+      let called = false;
+      if (__DEV__) {
+        if (typeof Promise !== 'undefined') {
+          //eslint-disable-next-line no-undef
+          Promise.resolve()
+            .then(() => {})
+            .then(() => {
+              if (called === false) {
+                warningWithoutStack(
+                  null,
+                  'You called act(async () => ...) without await. ' +
+                    'This could lead to unexpected testing behaviour, interleaving multiple act ' +
+                    'calls and mixing their scopes. You should - await act(async () => ...);',
+                );
+              }
+            });
+        }
+      }
+
+      // in the async case, the returned thenable runs the callback, flushes
+      // effects and  microtasks in a loop until flushPassiveEffects() === false,
+      // and cleans up
+      return {
+        then(resolve: () => void, reject: (?Error) => void) {
+          called = true;
+          result.then(
+            () => {
+              flushEffectsAndMicroTasks((err: ?Error) => {
+                onDone();
+                if (err) {
+                  reject(err);
+                } else {
+                  resolve();
+                }
+              });
+            },
+            err => {
+              onDone();
+              reject(err);
+            },
+          );
+        },
+      };
+    } else {
+      if (__DEV__) {
+        warningWithoutStack(
+          result === undefined,
+          'The callback passed to act(...) function ' +
+            'must return undefined, or a Promise. You returned %s',
+          result,
+        );
+      }
+
+      // flush effects until none remain, and cleanup
+      try {
+        while (flushPassiveEffects()) {}
+        onDone();
+      } catch (err) {
+        onDone();
+        throw err;
+      }
+
+      // in the sync case, the returned thenable only warns *if* await-ed
+      return {
+        then(resolve: () => void) {
+          if (__DEV__) {
+            warningWithoutStack(
+              false,
+              'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+            );
+          }
+          resolve();
+        },
+      };
+    }
+  }
+
+  // end act() implementation
+
   function childToJSX(child, text) {
     if (text !== null) {
       return text;
@@ -843,56 +976,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
 
     interactiveUpdates: NoopRenderer.interactiveUpdates,
 
-    // maybe this should exist only in the test file
-    act(callback: () => void): Thenable {
-      // note: keep these warning messages in sync with
-      // ReactTestRenderer.js and ReactTestUtils.js
-      let result = NoopRenderer.batchedUpdates(callback);
-      if (__DEV__) {
-        if (result !== undefined) {
-          let addendum;
-          if (result !== null && typeof result.then === 'function') {
-            addendum =
-              "\n\nIt looks like you wrote ReactNoop.act(async () => ...) or returned a Promise from it's callback. " +
-              'Putting asynchronous logic inside ReactNoop.act(...) is not supported.\n';
-          } else {
-            addendum = ' You returned: ' + result;
-          }
-          warningWithoutStack(
-            false,
-            'The callback passed to ReactNoop.act(...) function must not return anything.%s',
-            addendum,
-          );
-        }
-      }
-      ReactNoop.flushPassiveEffects();
-      // we want the user to not expect a return,
-      // but we want to warn if they use it like they can await on it.
-      return {
-        then() {
-          if (__DEV__) {
-            warningWithoutStack(
-              false,
-              'Do not await the result of calling ReactNoop.act(...), it is not a Promise.',
-            );
-          }
-        },
-      };
-    },
-
     flushSync(fn: () => mixed) {
       NoopRenderer.flushSync(fn);
     },
 
-    flushPassiveEffects() {
-      // Trick to flush passive effects without exposing an internal API:
-      // Create a throwaway root and schedule a dummy update on it.
-      const rootID = 'bloopandthenmoreletterstoavoidaconflict';
-      const container = {rootID: rootID, pendingChildren: [], children: []};
-      rootContainers.set(rootID, container);
-      const root = NoopRenderer.createContainer(container, true, false);
-      NoopRenderer.updateContainer(null, root, null, null);
-    },
+    flushPassiveEffects: NoopRenderer.flushPassiveEffects,
+
+    act,
 
     // Logs the current state of the tree.
     dumpTree(rootID: string = DEFAULT_ROOT_ID) {
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index 57d8a7c4edb71..80707120e95fc 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -30,10 +30,10 @@ import {
 } from './ReactHookEffectTags';
 import {
   scheduleWork,
-  warnIfNotCurrentlyBatchingInDev,
   computeExpirationForFiber,
   flushPassiveEffects,
   requestCurrentTime,
+  warnIfNotCurrentlyActingUpdatesInDev,
 } from './ReactFiberScheduler';
 
 import invariant from 'shared/invariant';
@@ -1046,19 +1046,6 @@ function updateMemo<T>(
   return nextValue;
 }
 
-// in a test-like environment, we want to warn if dispatchAction()
-// is called outside of a batchedUpdates/TestUtils.act(...) call.
-let shouldWarnForUnbatchedSetState = false;
-
-if (__DEV__) {
-  // jest isn't a 'global', it's just exposed to tests via a wrapped function
-  // further, this isn't a test file, so flow doesn't recognize the symbol. So...
-  // $FlowExpectedError - because requirements don't give a damn about your type sigs.
-  if ('undefined' !== typeof jest) {
-    shouldWarnForUnbatchedSetState = true;
-  }
-}
-
 function dispatchAction<S, A>(
   fiber: Fiber,
   queue: UpdateQueue<S, A>,
@@ -1178,8 +1165,11 @@ function dispatchAction<S, A>(
       }
     }
     if (__DEV__) {
-      if (shouldWarnForUnbatchedSetState === true) {
-        warnIfNotCurrentlyBatchingInDev(fiber);
+      // jest isn't a 'global', it's just exposed to tests via a wrapped function
+      // further, this isn't a test file, so flow doesn't recognize the symbol. So...
+      // $FlowExpectedError - because requirements don't give a damn about your type sigs.
+      if ('undefined' !== typeof jest) {
+        warnIfNotCurrentlyActingUpdatesInDev(fiber);
       }
     }
     scheduleWork(fiber, expirationTime);
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index 01e303c74bc42..eb98b19ce4f0e 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -310,6 +310,7 @@ export {
   flushInteractiveUpdates,
   flushControlled,
   flushSync,
+  flushPassiveEffects,
 };
 
 export function getPublicRootInstance(
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index 5bea74a78b5ff..df179b766e155 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -34,7 +34,7 @@ import {
   flushInteractiveUpdates as flushInteractiveUpdates_old,
   computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
   flushPassiveEffects as flushPassiveEffects_old,
-  warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_old,
+  warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
 } from './ReactFiberScheduler.old';
 
 import {
@@ -62,7 +62,7 @@ import {
   flushInteractiveUpdates as flushInteractiveUpdates_new,
   computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
   flushPassiveEffects as flushPassiveEffects_new,
-  warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_new,
+  warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
 } from './ReactFiberScheduler.new';
 
 export let requestCurrentTime = requestCurrentTime_old;
@@ -89,7 +89,7 @@ export let interactiveUpdates = interactiveUpdates_old;
 export let flushInteractiveUpdates = flushInteractiveUpdates_old;
 export let computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_old;
 export let flushPassiveEffects = flushPassiveEffects_old;
-export let warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_old;
+export let warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_old;
 
 if (enableNewScheduler) {
   requestCurrentTime = requestCurrentTime_new;
@@ -116,9 +116,9 @@ if (enableNewScheduler) {
   flushInteractiveUpdates = flushInteractiveUpdates_new;
   computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_new;
   flushPassiveEffects = flushPassiveEffects_new;
-  warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_new;
+  warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_new;
 }
 
 export type Thenable = {
-  then(resolve: () => mixed, reject?: () => mixed): mixed,
+  then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
 };
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.new.js b/packages/react-reconciler/src/ReactFiberScheduler.new.js
index e4df9dad4fe72..810cbd8847d8d 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.new.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.new.js
@@ -33,4 +33,4 @@ export const interactiveUpdates = notYetImplemented;
 export const flushInteractiveUpdates = notYetImplemented;
 export const computeUniqueAsyncExpiration = notYetImplemented;
 export const flushPassiveEffects = notYetImplemented;
-export const warnIfNotCurrentlyBatchingInDev = notYetImplemented;
+export const warnIfNotCurrentlyActingUpdatesInDev = notYetImplemented;
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.old.js b/packages/react-reconciler/src/ReactFiberScheduler.old.js
index 887305457f1d7..1a4b15b2bee99 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.old.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.old.js
@@ -176,10 +176,14 @@ const {
 } = Scheduler;
 
 export type Thenable = {
-  then(resolve: () => mixed, reject?: () => mixed): mixed,
+  then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
 };
 
-const {ReactCurrentDispatcher, ReactCurrentOwner} = ReactSharedInternals;
+const {
+  ReactCurrentDispatcher,
+  ReactCurrentOwner,
+  ReactShouldWarnActingUpdates,
+} = ReactSharedInternals;
 
 let didWarnAboutStateTransition;
 let didWarnSetStateChildContext;
@@ -610,6 +614,7 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
 }
 
 function flushPassiveEffects() {
+  const didFlushEffects = passiveEffectCallback !== null;
   if (passiveEffectCallbackHandle !== null) {
     cancelCallback(passiveEffectCallbackHandle);
   }
@@ -618,6 +623,7 @@ function flushPassiveEffects() {
     // to ensure tracing works correctly.
     passiveEffectCallback();
   }
+  return didFlushEffects;
 }
 
 function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
@@ -1836,9 +1842,20 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
   return root;
 }
 
-export function warnIfNotCurrentlyBatchingInDev(fiber: Fiber): void {
+// in a test-like environment, we want to warn if dispatchAction() is
+// called outside of a TestUtils.act(...)/batchedUpdates/render call.
+// so we have a a step counter for when we descend/ascend from
+// act() calls, and test on it for when to warn
+// It's a tuple with a single value. Look for shared/createAct to
+// see how we change the value inside act() calls
+
+export function warnIfNotCurrentlyActingUpdatesInDev(fiber: Fiber): void {
   if (__DEV__) {
-    if (isRendering === false && isBatchingUpdates === false) {
+    if (
+      isBatchingUpdates === false &&
+      isRendering === false &&
+      ReactShouldWarnActingUpdates.current === false
+    ) {
       warningWithoutStack(
         false,
         'An update to %s inside a test was not wrapped in act(...).\n\n' +
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index 455df5d557064..f3f00658b5d9d 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -1046,11 +1046,10 @@ describe('ReactHooks', () => {
 
     class Cls extends React.Component {
       render() {
-        act(() =>
-          _setState(() => {
-            ReactCurrentDispatcher.current.readContext(ThemeContext);
-          }),
+        _setState(() =>
+          ReactCurrentDispatcher.current.readContext(ThemeContext),
         );
+
         return null;
       }
     }
@@ -1062,13 +1061,7 @@ describe('ReactHooks', () => {
           <Cls />
         </React.Fragment>,
       ),
-    ).toWarnDev(
-      [
-        'Context can only be read while React is rendering',
-        'Render methods should be a pure function of props and state',
-      ],
-      {withoutStack: 1},
-    );
+    ).toWarnDev(['Context can only be read while React is rendering']);
   });
 
   it('warns when calling hooks inside useReducer', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
new file mode 100644
index 0000000000000..f3aba9b99881d
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ *
+ * @jest-environment node
+ */
+
+// sanity tests for ReactNoop.act()
+
+jest.useRealTimers();
+const React = require('react');
+const ReactNoop = require('react-noop-renderer');
+const Scheduler = require('scheduler');
+
+describe('ReactNoop.act()', () => {
+  it('can use act to flush effects', async () => {
+    function App(props) {
+      React.useEffect(props.callback);
+      return null;
+    }
+
+    let calledLog = [];
+    ReactNoop.act(() => {
+      ReactNoop.render(
+        <App
+          callback={() => {
+            calledLog.push(calledLog.length);
+          }}
+        />,
+      );
+    });
+    expect(Scheduler).toFlushWithoutYielding();
+    expect(calledLog).toEqual([0]);
+  });
+
+  it('should work with async/await', async () => {
+    function App() {
+      let [ctr, setCtr] = React.useState(0);
+      async function someAsyncFunction() {
+        Scheduler.yieldValue('stage 1');
+        await null;
+        Scheduler.yieldValue('stage 2');
+        setCtr(1);
+      }
+      React.useEffect(() => {
+        someAsyncFunction();
+      }, []);
+      return ctr;
+    }
+    await ReactNoop.act(async () => {
+      ReactNoop.act(() => {
+        ReactNoop.render(<App />);
+      });
+      await null;
+      expect(Scheduler).toFlushAndYield(['stage 1']);
+    });
+    expect(Scheduler).toHaveYielded(['stage 2']);
+    expect(Scheduler).toFlushWithoutYielding();
+    expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]);
+  });
+});
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index de53adb201935..20553ba821912 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -40,7 +40,7 @@ import {
 } from 'shared/ReactWorkTags';
 import invariant from 'shared/invariant';
 import ReactVersion from 'shared/ReactVersion';
-import warningWithoutStack from 'shared/warningWithoutStack';
+import act from './ReactTestRendererAct';
 
 import {getPublicInstance} from './ReactTestHostConfig';
 
@@ -65,11 +65,6 @@ type FindOptions = $Shape<{
 
 export type Predicate = (node: ReactTestInstance) => ?boolean;
 
-// for .act's return value
-type Thenable = {
-  then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
-
 const defaultTestOptions = {
   createNodeMock: function() {
     return null;
@@ -549,63 +544,11 @@ const ReactTestRendererFiber = {
     return entry;
   },
 
-  /* eslint-disable camelcase */
+  /* eslint-disable-next-line camelcase */
   unstable_batchedUpdates: batchedUpdates,
-  /* eslint-enable camelcase */
-
-  act(callback: () => void): Thenable {
-    // note: keep these warning messages in sync with
-    // createNoop.js and ReactTestUtils.js
-    let result = batchedUpdates(callback);
-    if (__DEV__) {
-      if (result !== undefined) {
-        let addendum;
-        if (result !== null && typeof result.then === 'function') {
-          addendum =
-            "\n\nIt looks like you wrote TestRenderer.act(async () => ...) or returned a Promise from it's callback. " +
-            'Putting asynchronous logic inside TestRenderer.act(...) is not supported.\n';
-        } else {
-          addendum = ' You returned: ' + result;
-        }
-        warningWithoutStack(
-          false,
-          'The callback passed to TestRenderer.act(...) function must not return anything.%s',
-          addendum,
-        );
-      }
-    }
-    flushPassiveEffects();
-    // we want the user to not expect a return,
-    // but we want to warn if they use it like they can await on it.
-    return {
-      then() {
-        if (__DEV__) {
-          warningWithoutStack(
-            false,
-            'Do not await the result of calling TestRenderer.act(...), it is not a Promise.',
-          );
-        }
-      },
-    };
-  },
-};
 
-// root used to flush effects during .act() calls
-const actRoot = createContainer(
-  {
-    children: [],
-    createNodeMock: defaultTestOptions.createNodeMock,
-    tag: 'CONTAINER',
-  },
-  true,
-  false,
-);
-
-function flushPassiveEffects() {
-  // Trick to flush passive effects without exposing an internal API:
-  // Create a throwaway root and schedule a dummy update on it.
-  updateContainer(null, actRoot, null, null);
-}
+  act,
+};
 
 const fiberToWrapper = new WeakMap();
 function wrapFiber(fiber: Fiber): ReactTestInstance {
diff --git a/packages/react-test-renderer/src/ReactTestRendererAct.js b/packages/react-test-renderer/src/ReactTestRendererAct.js
new file mode 100644
index 0000000000000..37ced3fb04c04
--- /dev/null
+++ b/packages/react-test-renderer/src/ReactTestRendererAct.js
@@ -0,0 +1,153 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
+
+import {
+  batchedUpdates,
+  flushPassiveEffects,
+} from 'react-reconciler/inline.test';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import warningWithoutStack from 'shared/warningWithoutStack';
+import enqueueTask from 'shared/enqueueTask';
+
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
+// this implementation should be exactly the same in
+// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+// we track the 'depth' of the act() calls with this counter,
+// so we can tell if any async act() calls try to run in parallel.
+let actingUpdatesScopeDepth = 0;
+
+function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+  try {
+    flushPassiveEffects();
+    enqueueTask(() => {
+      if (flushPassiveEffects()) {
+        flushEffectsAndMicroTasks(onDone);
+      } else {
+        onDone();
+      }
+    });
+  } catch (err) {
+    onDone(err);
+  }
+}
+
+function act(callback: () => Thenable) {
+  let previousActingUpdatesScopeDepth;
+  if (__DEV__) {
+    previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+    actingUpdatesScopeDepth++;
+    ReactShouldWarnActingUpdates.current = true;
+  }
+
+  function onDone() {
+    if (__DEV__) {
+      actingUpdatesScopeDepth--;
+      if (actingUpdatesScopeDepth === 0) {
+        ReactShouldWarnActingUpdates.current = false;
+      }
+      if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+        // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+        warningWithoutStack(
+          null,
+          'You seem to have overlapping act() calls, this is not supported. ' +
+            'Be sure to await previous act() calls before making a new one. ',
+        );
+      }
+    }
+  }
+
+  const result = batchedUpdates(callback);
+  if (
+    result !== null &&
+    typeof result === 'object' &&
+    typeof result.then === 'function'
+  ) {
+    // setup a boolean that gets set to true only
+    // once this act() call is await-ed
+    let called = false;
+    if (__DEV__) {
+      if (typeof Promise !== 'undefined') {
+        //eslint-disable-next-line no-undef
+        Promise.resolve()
+          .then(() => {})
+          .then(() => {
+            if (called === false) {
+              warningWithoutStack(
+                null,
+                'You called act(async () => ...) without await. ' +
+                  'This could lead to unexpected testing behaviour, interleaving multiple act ' +
+                  'calls and mixing their scopes. You should - await act(async () => ...);',
+              );
+            }
+          });
+      }
+    }
+
+    // in the async case, the returned thenable runs the callback, flushes
+    // effects and  microtasks in a loop until flushPassiveEffects() === false,
+    // and cleans up
+    return {
+      then(resolve: () => void, reject: (?Error) => void) {
+        called = true;
+        result.then(
+          () => {
+            flushEffectsAndMicroTasks((err: ?Error) => {
+              onDone();
+              if (err) {
+                reject(err);
+              } else {
+                resolve();
+              }
+            });
+          },
+          err => {
+            onDone();
+            reject(err);
+          },
+        );
+      },
+    };
+  } else {
+    if (__DEV__) {
+      warningWithoutStack(
+        result === undefined,
+        'The callback passed to act(...) function ' +
+          'must return undefined, or a Promise. You returned %s',
+        result,
+      );
+    }
+
+    // flush effects until none remain, and cleanup
+    try {
+      while (flushPassiveEffects()) {}
+      onDone();
+    } catch (err) {
+      onDone();
+      throw err;
+    }
+
+    // in the sync case, the returned thenable only warns *if* await-ed
+    return {
+      then(resolve: () => void) {
+        if (__DEV__) {
+          warningWithoutStack(
+            false,
+            'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+          );
+        }
+        resolve();
+      },
+    };
+  }
+}
+
+export default act;
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
index 0f7690e671f32..37820febaf67d 100644
--- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
+++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
@@ -1023,40 +1023,19 @@ describe('ReactTestRenderer', () => {
     ReactTestRenderer.create(<App />);
   });
 
-  describe('act', () => {
-    it('can use .act() to batch updates and effects', () => {
-      function App(props) {
-        React.useEffect(() => {
-          props.callback();
-        });
-        return null;
-      }
-      let called = false;
-      ReactTestRenderer.act(() => {
-        ReactTestRenderer.create(
-          <App
-            callback={() => {
-              called = true;
-            }}
-          />,
-        );
-      });
-
-      expect(called).toBe(true);
-    });
-    it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
-      // we warn when you try to load 2 renderers in the same 'scope'
-      // so as suggested, we call resetModules() to carry on with the test
-      jest.resetModules();
-      const {act} = require('react-dom/test-utils');
-      expect(() => {
-        expect(() => act(() => {})).toThrow('document is not defined');
-      }).toWarnDev(
-        [
-          'It looks like you called TestUtils.act(...) in a non-browser environment',
-        ],
-        {withoutStack: 1},
-      );
-    });
+  // we run this test here because we need a dom-less scope
+  it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
+    // we warn when you try to load 2 renderers in the same 'scope'
+    // so as suggested, we call resetModules() to carry on with the test
+    jest.resetModules();
+    const {act} = require('react-dom/test-utils');
+    expect(() => {
+      expect(() => act(() => {})).toThrow('document is not defined');
+    }).toWarnDev(
+      [
+        'It looks like you called ReactTestUtils.act(...) in a non-browser environment',
+      ],
+      {withoutStack: 1},
+    );
   });
 });
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
new file mode 100644
index 0000000000000..9390c4ccfc74d
--- /dev/null
+++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
@@ -0,0 +1,122 @@
+jest.useRealTimers();
+
+let React;
+let ReactTestRenderer;
+let Scheduler;
+let act;
+
+describe('ReactTestRenderer.act()', () => {
+  beforeEach(() => {
+    jest.resetModules();
+    React = require('react');
+    ReactTestRenderer = require('react-test-renderer');
+    Scheduler = require('scheduler');
+    act = ReactTestRenderer.act;
+  });
+  it('can use .act() to flush effects', () => {
+    function App(props) {
+      let [ctr, setCtr] = React.useState(0);
+      React.useEffect(() => {
+        props.callback();
+        setCtr(1);
+      }, []);
+      return ctr;
+    }
+    let calledLog = [];
+    let root;
+    act(() => {
+      root = ReactTestRenderer.create(
+        <App
+          callback={() => {
+            calledLog.push(calledLog.length);
+          }}
+        />,
+      );
+    });
+
+    expect(calledLog).toEqual([0]);
+    expect(root.toJSON()).toEqual('1');
+  });
+
+  it("warns if you don't use .act", () => {
+    let setCtr;
+    function App(props) {
+      let [ctr, _setCtr] = React.useState(0);
+      setCtr = _setCtr;
+      return ctr;
+    }
+
+    ReactTestRenderer.create(<App />);
+
+    expect(() => {
+      setCtr(1);
+    }).toWarnDev([
+      'An update to App inside a test was not wrapped in act(...)',
+    ]);
+  });
+
+  describe('async', () => {
+    it('should work with async/await', async () => {
+      function fetch(url) {
+        return Promise.resolve({
+          details: [1, 2, 3],
+        });
+      }
+      function App() {
+        let [details, setDetails] = React.useState(0);
+
+        React.useEffect(() => {
+          async function fetchDetails() {
+            const response = await fetch();
+            setDetails(response.details);
+          }
+          fetchDetails();
+        }, []);
+        return details;
+      }
+      let root;
+
+      await ReactTestRenderer.act(async () => {
+        root = ReactTestRenderer.create(<App />);
+      });
+
+      expect(root.toJSON()).toEqual(['1', '2', '3']);
+    });
+
+    it('should not flush effects without also flushing microtasks', async () => {
+      const {useEffect, useReducer} = React;
+
+      const alreadyResolvedPromise = Promise.resolve();
+
+      function App() {
+        // This component will keep updating itself until step === 3
+        const [step, proceed] = useReducer(s => (s === 3 ? 3 : s + 1), 1);
+        useEffect(() => {
+          Scheduler.yieldValue('Effect');
+          alreadyResolvedPromise.then(() => {
+            Scheduler.yieldValue('Microtask');
+            proceed();
+          });
+        });
+        return step;
+      }
+      const root = ReactTestRenderer.create(null);
+      await act(async () => {
+        root.update(<App />);
+      });
+      expect(Scheduler).toHaveYielded([
+        // Should not flush effects without also flushing microtasks
+        // First render:
+        'Effect',
+        'Microtask',
+        // Second render:
+        'Effect',
+        'Microtask',
+        // Final render:
+        'Effect',
+        'Microtask',
+      ]);
+      expect(root).toMatchRenderedOutput('3');
+    });
+  });
+});
diff --git a/packages/react/src/ReactSharedInternals.js b/packages/react/src/ReactSharedInternals.js
index 53dc43d977e51..c6d5010cb977d 100644
--- a/packages/react/src/ReactSharedInternals.js
+++ b/packages/react/src/ReactSharedInternals.js
@@ -15,6 +15,8 @@ import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
 const ReactSharedInternals = {
   ReactCurrentDispatcher,
   ReactCurrentOwner,
+  // used by act()
+  ReactShouldWarnActingUpdates: {current: false},
   // Used by renderers to avoid bundling object-assign twice in UMD bundles:
   assign,
 };
diff --git a/packages/shared/enqueueTask.js b/packages/shared/enqueueTask.js
new file mode 100644
index 0000000000000..02f4ee220672a
--- /dev/null
+++ b/packages/shared/enqueueTask.js
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+
+import warningWithoutStack from './warningWithoutStack';
+
+let didWarnAboutMessageChannel = false;
+let enqueueTask;
+try {
+  // assuming we're in node, let's try to get node's
+  // version of setImmediate, bypassing fake timers if any
+  let r = require; // trick packagers not to bundle this stuff.
+  enqueueTask = r('timers').setImmediate;
+} catch (_err) {
+  // we're in a browser
+  // we can't use regular timers because they may still be faked
+  // so we try MessageChannel+postMessage instead
+  enqueueTask = function(callback: () => void) {
+    if (__DEV__) {
+      if (didWarnAboutMessageChannel === false) {
+        didWarnAboutMessageChannel = true;
+        warningWithoutStack(
+          typeof MessageChannel !== 'undefined',
+          'This browser does not have a MessageChannel implementation, ' +
+            'so enqueuing tasks via await act(async () => ...) will fail. ' +
+            'Please file an issue at https://github.com/facebook/react/issues ' +
+            'if you encounter this warning.',
+        );
+      }
+    }
+    const channel = new MessageChannel();
+    channel.port1.onmessage = callback;
+    channel.port2.postMessage(undefined);
+  };
+}
+
+export default enqueueTask;
diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json
index ddb87f522b2e0..c858da46fff87 100644
--- a/scripts/rollup/results.json
+++ b/scripts/rollup/results.json
@@ -4,29 +4,29 @@
       "filename": "react.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react",
-      "size": 102492,
-      "gzip": 26625
+      "size": 103860,
+      "gzip": 26948
     },
     {
       "filename": "react.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react",
-      "size": 12609,
-      "gzip": 4834
+      "size": 12346,
+      "gzip": 4735
     },
     {
       "filename": "react.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react",
-      "size": 64139,
-      "gzip": 17318
+      "size": 65507,
+      "gzip": 17628
     },
     {
       "filename": "react.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react",
-      "size": 6834,
-      "gzip": 2814
+      "size": 6571,
+      "gzip": 2718
     },
     {
       "filename": "React-dev.js",
@@ -46,29 +46,29 @@
       "filename": "react-dom.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-dom",
-      "size": 832345,
-      "gzip": 188603
+      "size": 835612,
+      "gzip": 189016
     },
     {
       "filename": "react-dom.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-dom",
-      "size": 107683,
-      "gzip": 34867
+      "size": 107684,
+      "gzip": 34777
     },
     {
       "filename": "react-dom.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 826372,
-      "gzip": 186963
+      "size": 829867,
+      "gzip": 187373
     },
     {
       "filename": "react-dom.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-dom",
       "size": 107664,
-      "gzip": 34301
+      "gzip": 34273
     },
     {
       "filename": "ReactDOM-dev.js",
@@ -88,29 +88,29 @@
       "filename": "react-dom-test-utils.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-dom",
-      "size": 48620,
-      "gzip": 13278
+      "size": 53238,
+      "gzip": 14410
     },
     {
       "filename": "react-dom-test-utils.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-dom",
-      "size": 10184,
-      "gzip": 3732
+      "size": 10659,
+      "gzip": 3913
     },
     {
       "filename": "react-dom-test-utils.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 48334,
-      "gzip": 13206
+      "size": 52952,
+      "gzip": 14343
     },
     {
       "filename": "react-dom-test-utils.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-dom",
-      "size": 9954,
-      "gzip": 3660
+      "size": 10441,
+      "gzip": 3835
     },
     {
       "filename": "ReactTestUtils-dev.js",
@@ -123,8 +123,8 @@
       "filename": "react-dom-unstable-native-dependencies.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-dom",
-      "size": 62190,
-      "gzip": 16206
+      "size": 62213,
+      "gzip": 16213
     },
     {
       "filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -137,8 +137,8 @@
       "filename": "react-dom-unstable-native-dependencies.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 61854,
-      "gzip": 16078
+      "size": 61877,
+      "gzip": 16085
     },
     {
       "filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -165,29 +165,29 @@
       "filename": "react-dom-server.browser.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-dom",
-      "size": 136840,
-      "gzip": 36205
+      "size": 137930,
+      "gzip": 36351
     },
     {
       "filename": "react-dom-server.browser.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-dom",
-      "size": 19363,
-      "gzip": 7290
+      "size": 19569,
+      "gzip": 7381
     },
     {
       "filename": "react-dom-server.browser.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 132878,
-      "gzip": 35237
+      "size": 133968,
+      "gzip": 35384
     },
     {
       "filename": "react-dom-server.browser.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-dom",
-      "size": 19287,
-      "gzip": 7290
+      "size": 19495,
+      "gzip": 7377
     },
     {
       "filename": "ReactDOMServer-dev.js",
@@ -207,43 +207,43 @@
       "filename": "react-dom-server.node.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 134867,
-      "gzip": 35791
+      "size": 135957,
+      "gzip": 35941
     },
     {
       "filename": "react-dom-server.node.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-dom",
-      "size": 20170,
-      "gzip": 7598
+      "size": 20377,
+      "gzip": 7690
     },
     {
       "filename": "react-art.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-art",
-      "size": 570224,
-      "gzip": 123819
+      "size": 582400,
+      "gzip": 125635
     },
     {
       "filename": "react-art.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-art",
-      "size": 99370,
-      "gzip": 30598
+      "size": 99129,
+      "gzip": 30380
     },
     {
       "filename": "react-art.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-art",
-      "size": 499479,
-      "gzip": 106084
+      "size": 511655,
+      "gzip": 107868
     },
     {
       "filename": "react-art.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-art",
-      "size": 63450,
-      "gzip": 19455
+      "size": 63295,
+      "gzip": 19346
     },
     {
       "filename": "ReactART-dev.js",
@@ -291,29 +291,29 @@
       "filename": "react-test-renderer.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-test-renderer",
-      "size": 508633,
-      "gzip": 107760
+      "size": 525649,
+      "gzip": 110641
     },
     {
       "filename": "react-test-renderer.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-test-renderer",
-      "size": 64479,
-      "gzip": 19791
+      "size": 64570,
+      "gzip": 19714
     },
     {
       "filename": "react-test-renderer.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-test-renderer",
-      "size": 504043,
-      "gzip": 106587
+      "size": 521059,
+      "gzip": 109459
     },
     {
       "filename": "react-test-renderer.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-test-renderer",
-      "size": 64119,
-      "gzip": 19538
+      "size": 64265,
+      "gzip": 19537
     },
     {
       "filename": "ReactTestRenderer-dev.js",
@@ -326,29 +326,29 @@
       "filename": "react-test-renderer-shallow.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-test-renderer",
-      "size": 38113,
-      "gzip": 9733
+      "size": 39907,
+      "gzip": 10032
     },
     {
       "filename": "react-test-renderer-shallow.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-test-renderer",
-      "size": 11385,
-      "gzip": 3419
+      "size": 11688,
+      "gzip": 3579
     },
     {
       "filename": "react-test-renderer-shallow.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-test-renderer",
-      "size": 32275,
-      "gzip": 8333
+      "size": 33992,
+      "gzip": 8604
     },
     {
       "filename": "react-test-renderer-shallow.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-test-renderer",
-      "size": 12046,
-      "gzip": 3733
+      "size": 11884,
+      "gzip": 3709
     },
     {
       "filename": "ReactShallowRenderer-dev.js",
@@ -361,57 +361,57 @@
       "filename": "react-noop-renderer.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-noop-renderer",
-      "size": 26949,
-      "gzip": 6362
+      "size": 35270,
+      "gzip": 8615
     },
     {
       "filename": "react-noop-renderer.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-noop-renderer",
-      "size": 9933,
-      "gzip": 3165
+      "size": 10470,
+      "gzip": 3431
     },
     {
       "filename": "react-reconciler.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-reconciler",
-      "size": 497879,
-      "gzip": 104645
+      "size": 511941,
+      "gzip": 106665
     },
     {
       "filename": "react-reconciler.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-reconciler",
-      "size": 64551,
-      "gzip": 19325
+      "size": 64562,
+      "gzip": 19196
     },
     {
       "filename": "react-reconciler-persistent.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-reconciler",
-      "size": 495898,
-      "gzip": 103848
+      "size": 509778,
+      "gzip": 105781
     },
     {
       "filename": "react-reconciler-persistent.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-reconciler",
-      "size": 64562,
-      "gzip": 19330
+      "size": 64573,
+      "gzip": 19202
     },
     {
       "filename": "react-reconciler-reflection.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-reconciler",
       "size": 16161,
-      "gzip": 5096
+      "gzip": 5015
     },
     {
       "filename": "react-reconciler-reflection.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-reconciler",
-      "size": 2760,
-      "gzip": 1244
+      "size": 2423,
+      "gzip": 1082
     },
     {
       "filename": "react-call-return.development.js",
@@ -487,57 +487,57 @@
       "filename": "create-subscription.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "create-subscription",
-      "size": 8538,
-      "gzip": 2952
+      "size": 8219,
+      "gzip": 2827
     },
     {
       "filename": "create-subscription.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "create-subscription",
-      "size": 2889,
-      "gzip": 1344
+      "size": 2558,
+      "gzip": 1200
     },
     {
       "filename": "React-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react",
-      "size": 61163,
-      "gzip": 16239
+      "size": 63801,
+      "gzip": 16915
     },
     {
       "filename": "React-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react",
-      "size": 15734,
-      "gzip": 4189
+      "size": 15699,
+      "gzip": 4193
     },
     {
       "filename": "ReactDOM-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-dom",
-      "size": 851672,
-      "gzip": 188651
+      "size": 855350,
+      "gzip": 189054
     },
     {
       "filename": "ReactDOM-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react-dom",
-      "size": 339041,
-      "gzip": 62418
+      "size": 339926,
+      "gzip": 62708
     },
     {
       "filename": "ReactTestUtils-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-dom",
-      "size": 46251,
-      "gzip": 12476
+      "size": 51170,
+      "gzip": 13698
     },
     {
       "filename": "ReactDOMUnstableNativeDependencies-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-dom",
-      "size": 60296,
-      "gzip": 15251
+      "size": 60319,
+      "gzip": 15257
     },
     {
       "filename": "ReactDOMUnstableNativeDependencies-prod.js",
@@ -550,99 +550,99 @@
       "filename": "ReactDOMServer-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-dom",
-      "size": 135272,
-      "gzip": 35002
+      "size": 136328,
+      "gzip": 35148
     },
     {
       "filename": "ReactDOMServer-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react-dom",
-      "size": 46877,
-      "gzip": 10879
+      "size": 47596,
+      "gzip": 10982
     },
     {
       "filename": "ReactART-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-art",
-      "size": 509037,
-      "gzip": 105217
+      "size": 521820,
+      "gzip": 107152
     },
     {
       "filename": "ReactART-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react-art",
-      "size": 201874,
-      "gzip": 34245
+      "size": 202109,
+      "gzip": 34176
     },
     {
       "filename": "ReactNativeRenderer-dev.js",
       "bundleType": "RN_FB_DEV",
       "packageName": "react-native-renderer",
-      "size": 637829,
-      "gzip": 136280
+      "size": 645983,
+      "gzip": 137694
     },
     {
       "filename": "ReactNativeRenderer-prod.js",
       "bundleType": "RN_FB_PROD",
       "packageName": "react-native-renderer",
-      "size": 251569,
-      "gzip": 43973
+      "size": 252030,
+      "gzip": 44064
     },
     {
       "filename": "ReactNativeRenderer-dev.js",
       "bundleType": "RN_OSS_DEV",
       "packageName": "react-native-renderer",
-      "size": 637742,
-      "gzip": 136246
+      "size": 645895,
+      "gzip": 137660
     },
     {
       "filename": "ReactNativeRenderer-prod.js",
       "bundleType": "RN_OSS_PROD",
       "packageName": "react-native-renderer",
-      "size": 251583,
-      "gzip": 43970
+      "size": 252044,
+      "gzip": 44061
     },
     {
       "filename": "ReactFabric-dev.js",
       "bundleType": "RN_FB_DEV",
       "packageName": "react-native-renderer",
-      "size": 628028,
-      "gzip": 133874
+      "size": 634566,
+      "gzip": 134983
     },
     {
       "filename": "ReactFabric-prod.js",
       "bundleType": "RN_FB_PROD",
       "packageName": "react-native-renderer",
-      "size": 245497,
-      "gzip": 42746
+      "size": 245276,
+      "gzip": 42773
     },
     {
       "filename": "ReactFabric-dev.js",
       "bundleType": "RN_OSS_DEV",
       "packageName": "react-native-renderer",
-      "size": 627933,
-      "gzip": 133834
+      "size": 634470,
+      "gzip": 134930
     },
     {
       "filename": "ReactFabric-prod.js",
       "bundleType": "RN_OSS_PROD",
       "packageName": "react-native-renderer",
-      "size": 245503,
-      "gzip": 42741
+      "size": 245282,
+      "gzip": 42767
     },
     {
       "filename": "ReactTestRenderer-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-test-renderer",
-      "size": 514514,
-      "gzip": 106185
+      "size": 532823,
+      "gzip": 109328
     },
     {
       "filename": "ReactShallowRenderer-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-test-renderer",
-      "size": 30652,
-      "gzip": 7761
+      "size": 33767,
+      "gzip": 8435
     },
     {
       "filename": "ReactIs-dev.js",
@@ -704,36 +704,36 @@
       "filename": "react-noop-renderer-persistent.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-noop-renderer",
-      "size": 27068,
-      "gzip": 6375
+      "size": 35389,
+      "gzip": 8630
     },
     {
       "filename": "react-noop-renderer-persistent.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-noop-renderer",
-      "size": 9955,
-      "gzip": 3170
+      "size": 10492,
+      "gzip": 3436
     },
     {
       "filename": "react-dom.profiling.min.js",
       "bundleType": "NODE_PROFILING",
       "packageName": "react-dom",
-      "size": 110839,
-      "gzip": 34944
+      "size": 110841,
+      "gzip": 34901
     },
     {
       "filename": "ReactNativeRenderer-profiling.js",
       "bundleType": "RN_OSS_PROFILING",
       "packageName": "react-native-renderer",
-      "size": 257964,
-      "gzip": 45359
+      "size": 258447,
+      "gzip": 45443
     },
     {
       "filename": "ReactFabric-profiling.js",
       "bundleType": "RN_OSS_PROFILING",
       "packageName": "react-native-renderer",
-      "size": 250954,
-      "gzip": 44143
+      "size": 250755,
+      "gzip": 44122
     },
     {
       "filename": "Scheduler-dev.js",
@@ -760,50 +760,50 @@
       "filename": "React-profiling.js",
       "bundleType": "FB_WWW_PROFILING",
       "packageName": "react",
-      "size": 15734,
-      "gzip": 4189
+      "size": 15699,
+      "gzip": 4193
     },
     {
       "filename": "ReactDOM-profiling.js",
       "bundleType": "FB_WWW_PROFILING",
       "packageName": "react-dom",
-      "size": 345581,
-      "gzip": 63810
+      "size": 346531,
+      "gzip": 64085
     },
     {
       "filename": "ReactNativeRenderer-profiling.js",
       "bundleType": "RN_FB_PROFILING",
       "packageName": "react-native-renderer",
-      "size": 257945,
-      "gzip": 45363
+      "size": 258428,
+      "gzip": 45445
     },
     {
       "filename": "ReactFabric-profiling.js",
       "bundleType": "RN_FB_PROFILING",
       "packageName": "react-native-renderer",
-      "size": 250943,
-      "gzip": 44145
+      "size": 250744,
+      "gzip": 44126
     },
     {
       "filename": "react.profiling.min.js",
       "bundleType": "UMD_PROFILING",
       "packageName": "react",
-      "size": 14818,
-      "gzip": 5369
+      "size": 14552,
+      "gzip": 5255
     },
     {
       "filename": "react-dom.profiling.min.js",
       "bundleType": "UMD_PROFILING",
       "packageName": "react-dom",
-      "size": 110730,
-      "gzip": 35485
+      "size": 110732,
+      "gzip": 35443
     },
     {
       "filename": "scheduler-tracing.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "scheduler",
-      "size": 10878,
-      "gzip": 2594
+      "size": 11062,
+      "gzip": 2681
     },
     {
       "filename": "scheduler-tracing.production.min.js",
@@ -823,8 +823,8 @@
       "filename": "SchedulerTracing-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "scheduler",
-      "size": 10267,
-      "gzip": 2151
+      "size": 10470,
+      "gzip": 2260
     },
     {
       "filename": "SchedulerTracing-prod.js",
@@ -886,29 +886,29 @@
       "filename": "jest-react.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "jest-react",
-      "size": 7455,
-      "gzip": 2737
+      "size": 7100,
+      "gzip": 2546
     },
     {
       "filename": "jest-react.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "jest-react",
-      "size": 2930,
-      "gzip": 1442
+      "size": 2599,
+      "gzip": 1299
     },
     {
       "filename": "JestReact-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "jest-react",
-      "size": 4208,
-      "gzip": 1490
+      "size": 5010,
+      "gzip": 1757
     },
     {
       "filename": "JestReact-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "jest-react",
-      "size": 3592,
-      "gzip": 1315
+      "size": 3492,
+      "gzip": 1287
     },
     {
       "filename": "react-debug-tools.development.js",
@@ -928,15 +928,15 @@
       "filename": "eslint-plugin-react-hooks.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "eslint-plugin-react-hooks",
-      "size": 75099,
-      "gzip": 17223
+      "size": 77541,
+      "gzip": 17683
     },
     {
       "filename": "eslint-plugin-react-hooks.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "eslint-plugin-react-hooks",
-      "size": 19731,
-      "gzip": 6811
+      "size": 20485,
+      "gzip": 7082
     },
     {
       "filename": "ReactDOMFizzServer-dev.js",
@@ -1026,71 +1026,71 @@
       "filename": "ESLintPluginReactHooks-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "eslint-plugin-react-hooks",
-      "size": 80483,
-      "gzip": 17729
+      "size": 83133,
+      "gzip": 18239
     },
     {
       "filename": "react-dom-unstable-fire.development.js",
       "bundleType": "UMD_DEV",
       "packageName": "react-dom",
-      "size": 832471,
-      "gzip": 188731
+      "size": 835944,
+      "gzip": 189154
     },
     {
       "filename": "react-dom-unstable-fire.production.min.js",
       "bundleType": "UMD_PROD",
       "packageName": "react-dom",
-      "size": 107698,
-      "gzip": 34877
+      "size": 107699,
+      "gzip": 34786
     },
     {
       "filename": "react-dom-unstable-fire.profiling.min.js",
       "bundleType": "UMD_PROFILING",
       "packageName": "react-dom",
-      "size": 110745,
-      "gzip": 35493
+      "size": 110747,
+      "gzip": 35451
     },
     {
       "filename": "react-dom-unstable-fire.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-dom",
-      "size": 826725,
-      "gzip": 187104
+      "size": 830198,
+      "gzip": 187505
     },
     {
       "filename": "react-dom-unstable-fire.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-dom",
       "size": 107678,
-      "gzip": 34310
+      "gzip": 34282
     },
     {
       "filename": "react-dom-unstable-fire.profiling.min.js",
       "bundleType": "NODE_PROFILING",
       "packageName": "react-dom",
-      "size": 110853,
-      "gzip": 34953
+      "size": 110855,
+      "gzip": 34911
     },
     {
       "filename": "ReactFire-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-dom",
-      "size": 850863,
-      "gzip": 188551
+      "size": 854519,
+      "gzip": 189023
     },
     {
       "filename": "ReactFire-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react-dom",
-      "size": 327881,
-      "gzip": 60185
+      "size": 328314,
+      "gzip": 60295
     },
     {
       "filename": "ReactFire-profiling.js",
       "bundleType": "FB_WWW_PROFILING",
       "packageName": "react-dom",
-      "size": 334366,
-      "gzip": 61586
+      "size": 334864,
+      "gzip": 61709
     },
     {
       "filename": "jest-mock-scheduler.development.js",
@@ -1152,29 +1152,253 @@
       "filename": "react-events.development.js",
       "bundleType": "NODE_DEV",
       "packageName": "react-events",
-      "size": 1135,
-      "gzip": 623
+      "size": 990,
+      "gzip": 545
     },
     {
       "filename": "react-events.production.min.js",
       "bundleType": "NODE_PROD",
       "packageName": "react-events",
-      "size": 448,
-      "gzip": 328
+      "size": 506,
+      "gzip": 343
     },
     {
       "filename": "ReactEvents-dev.js",
       "bundleType": "FB_WWW_DEV",
       "packageName": "react-events",
-      "size": 1106,
-      "gzip": 613
+      "size": 956,
+      "gzip": 536
     },
     {
       "filename": "ReactEvents-prod.js",
       "bundleType": "FB_WWW_PROD",
       "packageName": "react-events",
-      "size": 643,
-      "gzip": 377
+      "size": 687,
+      "gzip": 410
+    },
+    {
+      "filename": "react-events.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 1183,
+      "gzip": 605
+    },
+    {
+      "filename": "react-events.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 676,
+      "gzip": 420
+    },
+    {
+      "filename": "react-events-press.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 10325,
+      "gzip": 2630
+    },
+    {
+      "filename": "react-events-press.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 4058,
+      "gzip": 1507
+    },
+    {
+      "filename": "react-events-press.development.js",
+      "bundleType": "NODE_DEV",
+      "packageName": "react-events",
+      "size": 10151,
+      "gzip": 2584
+    },
+    {
+      "filename": "react-events-press.production.min.js",
+      "bundleType": "NODE_PROD",
+      "packageName": "react-events",
+      "size": 3892,
+      "gzip": 1451
+    },
+    {
+      "filename": "ReactEventsPress-dev.js",
+      "bundleType": "FB_WWW_DEV",
+      "packageName": "react-events",
+      "size": 10480,
+      "gzip": 2636
+    },
+    {
+      "filename": "ReactEventsPress-prod.js",
+      "bundleType": "FB_WWW_PROD",
+      "packageName": "react-events",
+      "size": 8000,
+      "gzip": 1905
+    },
+    {
+      "filename": "react-events-hover.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 5271,
+      "gzip": 1416
+    },
+    {
+      "filename": "react-events-hover.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 2312,
+      "gzip": 923
+    },
+    {
+      "filename": "react-events-hover.development.js",
+      "bundleType": "NODE_DEV",
+      "packageName": "react-events",
+      "size": 5097,
+      "gzip": 1372
+    },
+    {
+      "filename": "react-events-hover.production.min.js",
+      "bundleType": "NODE_PROD",
+      "packageName": "react-events",
+      "size": 2147,
+      "gzip": 865
+    },
+    {
+      "filename": "ReactEventsHover-dev.js",
+      "bundleType": "FB_WWW_DEV",
+      "packageName": "react-events",
+      "size": 5113,
+      "gzip": 1386
+    },
+    {
+      "filename": "ReactEventsHover-prod.js",
+      "bundleType": "FB_WWW_PROD",
+      "packageName": "react-events",
+      "size": 4279,
+      "gzip": 1130
+    },
+    {
+      "filename": "react-events-focus.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 3446,
+      "gzip": 1112
+    },
+    {
+      "filename": "react-events-focus.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 1563,
+      "gzip": 721
+    },
+    {
+      "filename": "react-events-focus.development.js",
+      "bundleType": "NODE_DEV",
+      "packageName": "react-events",
+      "size": 3272,
+      "gzip": 1068
+    },
+    {
+      "filename": "react-events-focus.production.min.js",
+      "bundleType": "NODE_PROD",
+      "packageName": "react-events",
+      "size": 1392,
+      "gzip": 659
+    },
+    {
+      "filename": "ReactEventsFocus-dev.js",
+      "bundleType": "FB_WWW_DEV",
+      "packageName": "react-events",
+      "size": 3242,
+      "gzip": 1058
+    },
+    {
+      "filename": "ReactEventsFocus-prod.js",
+      "bundleType": "FB_WWW_PROD",
+      "packageName": "react-events",
+      "size": 2552,
+      "gzip": 827
+    },
+    {
+      "filename": "react-events-swipe.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 8479,
+      "gzip": 2604
+    },
+    {
+      "filename": "react-events-swipe.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 3531,
+      "gzip": 1625
+    },
+    {
+      "filename": "react-events-swipe.development.js",
+      "bundleType": "NODE_DEV",
+      "packageName": "react-events",
+      "size": 8305,
+      "gzip": 2571
+    },
+    {
+      "filename": "react-events-swipe.production.min.js",
+      "bundleType": "NODE_PROD",
+      "packageName": "react-events",
+      "size": 3364,
+      "gzip": 1569
+    },
+    {
+      "filename": "ReactEventsSwipe-dev.js",
+      "bundleType": "FB_WWW_DEV",
+      "packageName": "react-events",
+      "size": 6360,
+      "gzip": 1814
+    },
+    {
+      "filename": "ReactEventsSwipe-prod.js",
+      "bundleType": "FB_WWW_PROD",
+      "packageName": "react-events",
+      "size": 6089,
+      "gzip": 1563
+    },
+    {
+      "filename": "react-events-drag.development.js",
+      "bundleType": "UMD_DEV",
+      "packageName": "react-events",
+      "size": 7733,
+      "gzip": 2450
+    },
+    {
+      "filename": "react-events-drag.production.min.js",
+      "bundleType": "UMD_PROD",
+      "packageName": "react-events",
+      "size": 3278,
+      "gzip": 1489
+    },
+    {
+      "filename": "react-events-drag.development.js",
+      "bundleType": "NODE_DEV",
+      "packageName": "react-events",
+      "size": 7560,
+      "gzip": 2415
+    },
+    {
+      "filename": "react-events-drag.production.min.js",
+      "bundleType": "NODE_PROD",
+      "packageName": "react-events",
+      "size": 3112,
+      "gzip": 1429
+    },
+    {
+      "filename": "ReactEventsDrag-dev.js",
+      "bundleType": "FB_WWW_DEV",
+      "packageName": "react-events",
+      "size": 5706,
+      "gzip": 1684
+    },
+    {
+      "filename": "ReactEventsDrag-prod.js",
+      "bundleType": "FB_WWW_PROD",
+      "packageName": "react-events",
+      "size": 5245,
+      "gzip": 1368
     }
   ]
 }
\ No newline at end of file