@samitouri / QOS-React / commits / aed0e1c30c

await act(async () => ...) (#14853)

This took a while, but I'm happy I went through it. Some key moments - recursively flushing effects, flushing microtasks on each async turn, and my team's uncompromising philosophy on code reuse. Really happy with this. I still want to expand test coverage, and I have some more small related todos, but this is good to land. On to the next one. Soundtrack to landing this - https://open.spotify.com/track/0MF8I8OUo8kytiOo8aSHYq?si=gSWqUheKQbiQDXzptCXHTg * hacked up act(async () => {...}) * move stuff around * merge changes * abstract .act warnings and stuff. all renderers. pass all tests. * move testutils.act back into testutils * move into scheduler, rename some bits * smaller bundle * a comment for why we don't do typeof === 'function' * fix test * pass tests - fire, prod * lose actContainerElement * tighter * write a test for TestRenderer it's an odd one, because not only does sync act not flush effects correctly, but the async one does (wut). verified it's fine with the dom version. * lint * rewrote to move flushing logic closer to the renderer the scheduler's `flushPassiveEffects` didn't work as expected for the test renderer, so I decided to go back to the hack (rendering a dumb container) This also makes reactdom not as heavy (by a few bytes, but still). * move it around so the delta isn't too bad * cleanups fix promise chaining propagate errors correctly test for thenable the 'right' way more tests! tidier! ponies! * Stray comment * recursively flush effects * fixed tests * lint, move noop.act into react-reconciler * microtasks when checking if called, s/called/calledLog, cleanup * pass fb lint we could have globally changed our eslint config to assume Promise is available, but that means we expect a promise polyfill on the page, and we don't yet. this code is triggered only in jest anyway, and we're fairly certain Promise will be available there. hence, the once-off disable for the check * shorter timers, fix a test, test for Promise * use global.Promise for existence check * flush microtasks * a version that works in browsers (that support postMessage) I also added a sanity fixture inside fixtures/dom/ mostly for me. * hoist flushEffectsAndMicroTasks * pull out tick logic from ReactFiberScheduler * fix await act (...sync) hanging - fix a hang when awaiting sync logic - a better async/await test for test renderer * feedback changes - use node's setImmediate if available - a warning if MessageChannel isn't available - rename some functions * pass lint/flow checks (without requiring a Promise polyfill/exclusion) * prettier the prettiest, even. * use globalPromise for the missed await warning * __DEV__ check for didWarnAboutMessageChannel * thenables and callbacks instead of promises, pass flow/lint * tinier. better. - pulled most bits out of FiberScheduler - actedUpdates uses callbacks now * pass build validation * augh prettier * golfing 7 more chars * Test that effects are not flushed without also flushing microtasks * export doesHavePendingPassiveEffects, nits * createAct() * dead code * missed in merge? * lose the preflushing bits * ugh prettier * removed `actedUpdates()`, created shared/actingUpdatesScopeDepth * rearrange imports so builds work, remove the hack versions of flushPassiveEffects * represent actingUpdatesScopeDepth as a tuple [number] * use a shared flag on React.__SECRET... * remove createAct, setup act for all relevant renderers * review feedback shared/enqueueTask import ReactSharedInternals from 'shared/ReactSharedInternals'; simpler act() internals ReactSharedInternals.ReactShouldWarnActingUpdates * move act() implementation into createReactNoop * warnIfNotCurrentlyActingUpdatesInDev condition check order

Sunil Pai committed Apr 2, 2019 at 22:33 UTC aed0e1c30c113cbf748eb312b9f1bf0f598c79a1
25 files changed +1639 -600
fixtures/dom/.gitignore
+2
@@ -14,6 +14,8 @@ public/react-dom.development.js
14 public/react-dom.production.min.js
15 public/react-dom-server.browser.development.js
16 public/react-dom-server.browser.production.min.js
17 +public/react-dom-test-utils.development.js
18 +public/react-dom-test-utils.production.min.js
19
20 # misc
21 .DS_Store
fixtures/dom/package.json
+1 -1
@@ -18,7 +18,7 @@
18 },
19 "scripts": {
20 "start": "react-scripts start",
21 - "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/",
21 + "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/",
22 "build": "react-scripts build && cp build/index.html build/200.html",
23 "test": "react-scripts test --env=jsdom",
24 "eject": "react-scripts eject"
fixtures/dom/public/act-dom.html new
+41
@@ -0,0 +1,41 @@
1 +<!DOCTYPE html>
2 +<html>
3 +<head>
4 + <title>sanity test for ReactTestUtils.act</title>
5 +</head>
6 +<body>
7 + this page tests whether act runs properly in a browser.
8 + <br/>
9 + your console should say "5"
10 + <script src='react.development.js'></script>
11 + <script src='react-dom.development.js'></script>
12 + <script src='react-dom-test-utils.development.js'></script>
13 + <script>
14 + async function run(){
15 + // from ReactTestUtilsAct-test.js
16 + function App() {
17 + let [state, setState] = React.useState(0);
18 + async function ticker() {
19 + await null;
20 + setState(x => x + 1);
21 + }
22 + React.useEffect(
23 + () => {
24 + ticker();
25 + },
26 + [Math.min(state, 4)],
27 + );
28 + return state;
29 + }
30 + const el = document.createElement('div');
31 + await ReactTestUtils.act(async () => {
32 + ReactDOM.render(React.createElement(App), el);
33 + });
34 + // all 5 ticks present and accounted for
35 + console.log(el.innerHTML);
36 + }
37 + run();
38 +
39 + </script>
40 +</body>
41 +</html>
packages/react-dom/src/__tests__/ReactTestUtils-test.js
-171
@@ -14,7 +14,6 @@ let React;
14 let ReactDOM;
15 let ReactDOMServer;
16 let ReactTestUtils;
17 -let act;
17
18 function getTestDocument(markup) {
19 const doc = document.implementation.createHTMLDocument('');
@@ -34,7 +33,6 @@ describe('ReactTestUtils', () => {
33 ReactDOM = require('react-dom');
34 ReactDOMServer = require('react-dom/server');
35 ReactTestUtils = require('react-dom/test-utils');
37 - act = ReactTestUtils.act;
36 });
37
38 it('Simulate should have locally attached media events', () => {
@@ -517,173 +515,4 @@ describe('ReactTestUtils', () => {
515 ReactTestUtils.renderIntoDocument(<Component />);
516 expect(mockArgs.length).toEqual(0);
517 });
520 -
521 - it('can use act to batch effects', () => {
522 - function App(props) {
523 - React.useEffect(props.callback);
524 - return null;
525 - }
526 - const container = document.createElement('div');
527 - document.body.appendChild(container);
528 -
529 - try {
530 - let called = false;
531 - act(() => {
532 - ReactDOM.render(
533 - <App
534 - callback={() => {
535 - called = true;
536 - }}
537 - />,
538 - container,
539 - );
540 - });
541 -
542 - expect(called).toBe(true);
543 - } finally {
544 - document.body.removeChild(container);
545 - }
546 - });
547 -
548 - it('flushes effects on every call', () => {
549 - function App(props) {
550 - let [ctr, setCtr] = React.useState(0);
551 - React.useEffect(() => {
552 - props.callback(ctr);
553 - });
554 - return (
555 - <button id="button" onClick={() => setCtr(x => x + 1)}>
556 - click me!
557 - </button>
558 - );
559 - }
560 -
561 - const container = document.createElement('div');
562 - document.body.appendChild(container);
563 - let calledCtr = 0;
564 - act(() => {
565 - ReactDOM.render(
566 - <App
567 - callback={val => {
568 - calledCtr = val;
569 - }}
570 - />,
571 - container,
572 - );
573 - });
574 - const button = document.getElementById('button');
575 - function click() {
576 - button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
577 - }
578 -
579 - act(() => {
580 - click();
581 - click();
582 - click();
583 - });
584 - expect(calledCtr).toBe(3);
585 - act(click);
586 - expect(calledCtr).toBe(4);
587 - act(click);
588 - expect(calledCtr).toBe(5);
589 -
590 - document.body.removeChild(container);
591 - });
592 -
593 - it('can use act to batch effects on updates too', () => {
594 - function App() {
595 - let [ctr, setCtr] = React.useState(0);
596 - return (
597 - <button id="button" onClick={() => setCtr(x => x + 1)}>
598 - {ctr}
599 - </button>
600 - );
601 - }
602 - const container = document.createElement('div');
603 - document.body.appendChild(container);
604 - let button;
605 - act(() => {
606 - ReactDOM.render(<App />, container);
607 - });
608 - button = document.getElementById('button');
609 - expect(button.innerHTML).toBe('0');
610 - act(() => {
611 - button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
612 - });
613 - expect(button.innerHTML).toBe('1');
614 - document.body.removeChild(container);
615 - });
616 -
617 - it('detects setState being called outside of act(...)', () => {
618 - let setValueRef = null;
619 - function App() {
620 - let [value, setValue] = React.useState(0);
621 - setValueRef = setValue;
622 - return (
623 - <button id="button" onClick={() => setValue(2)}>
624 - {value}
625 - </button>
626 - );
627 - }
628 - const container = document.createElement('div');
629 - document.body.appendChild(container);
630 - let button;
631 - act(() => {
632 - ReactDOM.render(<App />, container);
633 - button = container.querySelector('#button');
634 - button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
635 - });
636 - expect(button.innerHTML).toBe('2');
637 - expect(() => setValueRef(1)).toWarnDev([
638 - 'An update to App inside a test was not wrapped in act(...).',
639 - ]);
640 - document.body.removeChild(container);
641 - });
642 -
643 - it('lets a ticker update', () => {
644 - function App() {
645 - let [toggle, setToggle] = React.useState(0);
646 - React.useEffect(() => {
647 - let timeout = setTimeout(() => {
648 - setToggle(1);
649 - }, 200);
650 - return () => clearTimeout(timeout);
651 - });
652 - return toggle;
653 - }
654 - const container = document.createElement('div');
655 -
656 - act(() => {
657 - act(() => {
658 - ReactDOM.render(<App />, container);
659 - });
660 - jest.advanceTimersByTime(250);
661 - });
662 -
663 - expect(container.innerHTML).toBe('1');
664 - });
665 -
666 - it('warns if you return a value inside act', () => {
667 - expect(() => act(() => null)).toWarnDev(
668 - [
669 - 'The callback passed to ReactTestUtils.act(...) function must not return anything.',
670 - ],
671 - {withoutStack: true},
672 - );
673 - expect(() => act(() => 123)).toWarnDev(
674 - [
675 - 'The callback passed to ReactTestUtils.act(...) function must not return anything.',
676 - ],
677 - {withoutStack: true},
678 - );
679 - });
680 -
681 - it('warns if you try to await an .act call', () => {
682 - expect(act(() => {}).then).toWarnDev(
683 - [
684 - 'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
685 - ],
686 - {withoutStack: true},
687 - );
688 - });
518 });
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js new
+403
@@ -0,0 +1,403 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +let React;
11 +let ReactDOM;
12 +let ReactTestUtils;
13 +let act;
14 +
15 +jest.useRealTimers();
16 +
17 +function sleep(period) {
18 + return new Promise(resolve => {
19 + setTimeout(() => {
20 + resolve(true);
21 + }, period);
22 + });
23 +}
24 +
25 +describe('ReactTestUtils.act()', () => {
26 + beforeEach(() => {
27 + jest.resetModules();
28 + React = require('react');
29 + ReactDOM = require('react-dom');
30 + ReactTestUtils = require('react-dom/test-utils');
31 + act = ReactTestUtils.act;
32 + });
33 +
34 + describe('sync', () => {
35 + it('can use act to flush effects', () => {
36 + function App(props) {
37 + React.useEffect(props.callback);
38 + return null;
39 + }
40 +
41 + let calledLog = [];
42 + act(() => {
43 + ReactDOM.render(
44 + <App
45 + callback={() => {
46 + calledLog.push(calledLog.length);
47 + }}
48 + />,
49 + document.createElement('div'),
50 + );
51 + });
52 +
53 + expect(calledLog).toEqual([0]);
54 + });
55 +
56 + it('flushes effects on every call', () => {
57 + function App(props) {
58 + let [ctr, setCtr] = React.useState(0);
59 + React.useEffect(() => {
60 + props.callback(ctr);
61 + });
62 + return (
63 + <button id="button" onClick={() => setCtr(x => x + 1)}>
64 + {ctr}
65 + </button>
66 + );
67 + }
68 +
69 + const container = document.createElement('div');
70 + // attach to body so events works
71 + document.body.appendChild(container);
72 + let calledCounter = 0;
73 + act(() => {
74 + ReactDOM.render(
75 + <App
76 + callback={val => {
77 + calledCounter = val;
78 + }}
79 + />,
80 + container,
81 + );
82 + });
83 + const button = document.getElementById('button');
84 + function click() {
85 + button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
86 + }
87 +
88 + act(() => {
89 + click();
90 + click();
91 + click();
92 + });
93 + expect(calledCounter).toBe(3);
94 + act(click);
95 + expect(calledCounter).toBe(4);
96 + act(click);
97 + expect(calledCounter).toBe(5);
98 + expect(button.innerHTML).toBe('5');
99 +
100 + document.body.removeChild(container);
101 + });
102 +
103 + it('should flush effects recursively', () => {
104 + function App() {
105 + let [ctr, setCtr] = React.useState(0);
106 + React.useEffect(() => {
107 + if (ctr < 5) {
108 + setCtr(x => x + 1);
109 + }
110 + });
111 + return ctr;
112 + }
113 +
114 + const container = document.createElement('div');
115 + act(() => {
116 + ReactDOM.render(<App />, container);
117 + });
118 +
119 + expect(container.innerHTML).toBe('5');
120 + });
121 +
122 + it('detects setState being called outside of act(...)', () => {
123 + let setValue = null;
124 + function App() {
125 + let [value, _setValue] = React.useState(0);
126 + setValue = _setValue;
127 + return (
128 + <button id="button" onClick={() => setValue(2)}>
129 + {value}
130 + </button>
131 + );
132 + }
133 + const container = document.createElement('div');
134 + document.body.appendChild(container);
135 + let button;
136 + act(() => {
137 + ReactDOM.render(<App />, container);
138 + button = container.querySelector('#button');
139 + button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
140 + });
141 + expect(button.innerHTML).toBe('2');
142 + expect(() => setValue(1)).toWarnDev([
143 + 'An update to App inside a test was not wrapped in act(...).',
144 + ]);
145 + document.body.removeChild(container);
146 + });
147 + describe('fake timers', () => {
148 + beforeEach(() => {
149 + jest.useFakeTimers();
150 + });
151 + afterEach(() => {
152 + jest.useRealTimers();
153 + });
154 + it('lets a ticker update', () => {
155 + function App() {
156 + let [toggle, setToggle] = React.useState(0);
157 + React.useEffect(() => {
158 + let timeout = setTimeout(() => {
159 + setToggle(1);
160 + }, 200);
161 + return () => clearTimeout(timeout);
162 + }, []);
163 + return toggle;
164 + }
165 + const container = document.createElement('div');
166 +
167 + act(() => {
168 + ReactDOM.render(<App />, container);
169 + });
170 + act(() => {
171 + jest.runAllTimers();
172 + });
173 +
174 + expect(container.innerHTML).toBe('1');
175 + });
176 + it('can use the async version to catch microtasks', async () => {
177 + function App() {
178 + let [toggle, setToggle] = React.useState(0);
179 + React.useEffect(() => {
180 + // just like the previous test, except we
181 + // use a promise and schedule the update
182 + // after it resolves
183 + sleep(200).then(() => setToggle(1));
184 + }, []);
185 + return toggle;
186 + }
187 + const container = document.createElement('div');
188 +
189 + act(() => {
190 + ReactDOM.render(<App />, container);
191 + });
192 + await act(async () => {
193 + jest.runAllTimers();
194 + });
195 +
196 + expect(container.innerHTML).toBe('1');
197 + });
198 + it('can handle cascading promises with fake timers', async () => {
199 + // this component triggers an effect, that waits a tick,
200 + // then sets state. repeats this 5 times.
201 + function App() {
202 + let [state, setState] = React.useState(0);
203 + async function ticker() {
204 + await null;
205 + setState(x => x + 1);
206 + }
207 + React.useEffect(
208 + () => {
209 + ticker();
210 + },
211 + [Math.min(state, 4)],
212 + );
213 + return state;
214 + }
215 + const el = document.createElement('div');
216 + await act(async () => {
217 + ReactDOM.render(<App />, el);
218 + });
219 +
220 + // all 5 ticks present and accounted for
221 + expect(el.innerHTML).toBe('5');
222 + });
223 + });
224 +
225 + it('warns if you return a value inside act', () => {
226 + expect(() => act(() => null)).toWarnDev(
227 + [
228 + 'The callback passed to act(...) function must return undefined, or a Promise.',
229 + ],
230 + {withoutStack: true},
231 + );
232 + expect(() => act(() => 123)).toWarnDev(
233 + [
234 + 'The callback passed to act(...) function must return undefined, or a Promise.',
235 + ],
236 + {withoutStack: true},
237 + );
238 + });
239 +
240 + it('warns if you try to await an .act call', () => {
241 + expect(() => act(() => {}).then(() => {})).toWarnDev(
242 + [
243 + 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
244 + ],
245 + {withoutStack: true},
246 + );
247 + });
248 + });
249 + describe('asynchronous tests', () => {
250 + it('can handle timers', async () => {
251 + function App() {
252 + let [ctr, setCtr] = React.useState(0);
253 + function doSomething() {
254 + setTimeout(() => {
255 + setCtr(1);
256 + }, 50);
257 + }
258 +
259 + React.useEffect(() => {
260 + doSomething();
261 + }, []);
262 + return ctr;
263 + }
264 + const el = document.createElement('div');
265 + await act(async () => {
266 + act(() => {
267 + ReactDOM.render(<App />, el);
268 + });
269 +
270 + await sleep(100);
271 + expect(el.innerHTML).toBe('1');
272 + });
273 + });
274 +
275 + it('can handle async/await', async () => {
276 + function App() {
277 + let [ctr, setCtr] = React.useState(0);
278 + async function someAsyncFunction() {
279 + // queue a bunch of promises to be sure they all flush
280 + await null;
281 + await null;
282 + await null;
283 + setCtr(1);
284 + }
285 + React.useEffect(() => {
286 + someAsyncFunction();
287 + }, []);
288 + return ctr;
289 + }
290 + const el = document.createElement('div');
291 +
292 + await act(async () => {
293 + act(() => {
294 + ReactDOM.render(<App />, el);
295 + });
296 + // pending promises will close before this ends
297 + });
298 + expect(el.innerHTML).toEqual('1');
299 + });
300 +
301 + it('warns if you do not await an act call', async () => {
302 + spyOnDevAndProd(console, 'error');
303 + act(async () => {});
304 + // it's annoying that we have to wait a tick before this warning comes in
305 + await sleep(0);
306 + if (__DEV__) {
307 + expect(console.error.calls.count()).toEqual(1);
308 + expect(console.error.calls.argsFor(0)[0]).toMatch(
309 + 'You called act(async () => ...) without await.',
310 + );
311 + }
312 + });
313 +
314 + it('warns if you try to interleave multiple act calls', async () => {
315 + spyOnDevAndProd(console, 'error');
316 + // let's try to cheat and spin off a 'thread' with an act call
317 + (async () => {
318 + await act(async () => {
319 + await sleep(50);
320 + });
321 + })();
322 +
323 + await act(async () => {
324 + await sleep(100);
325 + });
326 +
327 + await sleep(150);
328 + if (__DEV__) {
329 + expect(console.error).toHaveBeenCalledTimes(1);
330 + }
331 + });
332 +
333 + it('commits and effects are guaranteed to be flushed', async () => {
334 + function App(props) {
335 + let [state, setState] = React.useState(0);
336 + async function something() {
337 + await null;
338 + setState(1);
339 + }
340 + React.useEffect(() => {
341 + something();
342 + }, []);
343 + React.useEffect(() => {
344 + props.callback();
345 + });
346 + return state;
347 + }
348 + let ctr = 0;
349 + const div = document.createElement('div');
350 +
351 + await act(async () => {
352 + act(() => {
353 + ReactDOM.render(<App callback={() => ctr++} />, div);
354 + });
355 + expect(div.innerHTML).toBe('0');
356 + expect(ctr).toBe(1);
357 + });
358 + // this may seem odd, but it matches user behaviour -
359 + // a flash of "0" followed by "1"
360 +
361 + expect(div.innerHTML).toBe('1');
362 + expect(ctr).toBe(2);
363 + });
364 +
365 + it('propagates errors', async () => {
366 + let err;
367 + try {
368 + await act(async () => {
369 + throw new Error('some error');
370 + });
371 + } catch (_err) {
372 + err = _err;
373 + } finally {
374 + expect(err instanceof Error).toBe(true);
375 + expect(err.message).toBe('some error');
376 + }
377 + });
378 + it('can handle cascading promises', async () => {
379 + // this component triggers an effect, that waits a tick,
380 + // then sets state. repeats this 5 times.
381 + function App() {
382 + let [state, setState] = React.useState(0);
383 + async function ticker() {
384 + await null;
385 + setState(x => x + 1);
386 + }
387 + React.useEffect(
388 + () => {
389 + ticker();
390 + },
391 + [Math.min(state, 4)],
392 + );
393 + return state;
394 + }
395 + const el = document.createElement('div');
396 + await act(async () => {
397 + ReactDOM.render(<App />, el);
398 + });
399 + // all 5 ticks present and accounted for
400 + expect(el.innerHTML).toBe('5');
401 + });
402 + });
403 +});
packages/react-dom/src/client/ReactDOM.js
+3 -1
@@ -35,6 +35,7 @@ import {
35 getPublicRootInstance,
36 findHostInstance,
37 findHostInstanceWithWarning,
38 + flushPassiveEffects,
39 } from 'react-reconciler/inline.dom';
40 import {createPortal as createPortalImpl} from 'shared/ReactPortal';
41 import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -807,7 +808,7 @@ const ReactDOM: Object = {
808
809 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
810 // Keep in sync with ReactDOMUnstableNativeDependencies.js
810 - // and ReactTestUtils.js. This is an array for better minification.
811 + // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.
812 Events: [
813 getInstanceFromNode,
814 getNodeFromInstance,
@@ -820,6 +821,7 @@ const ReactDOM: Object = {
821 restoreStateIfNeeded,
822 dispatchEvent,
823 runEventsInBatch,
824 + flushPassiveEffects,
825 ],
826 },
827 };
packages/react-dom/src/fire/ReactFire.js
+2
@@ -40,6 +40,7 @@ import {
40 getPublicRootInstance,
41 findHostInstance,
42 findHostInstanceWithWarning,
43 + flushPassiveEffects,
44 } from 'react-reconciler/inline.fire';
45 import {createPortal as createPortalImpl} from 'shared/ReactPortal';
46 import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -826,6 +827,7 @@ const ReactDOM: Object = {
827 restoreStateIfNeeded,
828 dispatchEvent,
829 runEventsInBatch,
830 + flushPassiveEffects,
831 ],
832 },
833 };
packages/react-dom/src/test-utils/ReactTestUtils.js
+22 -53
@@ -4,6 +4,7 @@
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7 +import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
8
9 import React from 'react';
10 import ReactDOM from 'react-dom';
@@ -22,15 +23,11 @@ import warningWithoutStack from 'shared/warningWithoutStack';
23 import {ELEMENT_NODE} from '../shared/HTMLNodeType';
24 import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
25 import {PLUGIN_EVENT_SYSTEM} from 'events/EventSystemFlags';
25 -
26 -// for .act's return value
27 -type Thenable = {
28 - then(resolve: () => mixed, reject?: () => mixed): mixed,
29 -};
26 +import act from './ReactTestUtilsAct';
27
28 const {findDOMNode} = ReactDOM;
29 // Keep in sync with ReactDOMUnstableNativeDependencies.js
33 -// and ReactDOM.js:
30 +// ReactDOM.js, and ReactTestUtilsAct.js:
31 const [
32 getInstanceFromNode,
33 /* eslint-disable no-unused-vars */
@@ -45,6 +42,8 @@ const [
42 restoreStateIfNeeded,
43 dispatchEvent,
44 runEventsInBatch,
45 + // eslint-disable-next-line no-unused-vars
46 + flushPassiveEffects,
47 ] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
48
49 function Event(suffix) {}
@@ -152,9 +151,12 @@ function validateClassInstance(inst, methodName) {
151 );
152 }
153
155 -// a stub element, lazily initialized, used by act() when flushing effects
154 +// a plain dom element, lazily initialized, used by act() when flushing effects
155 let actContainerElement = null;
156
157 +// a warning for when you try to use TestUtils.act in a non-browser environment
158 +let didWarnAboutActInNodejs = false;
159 +
160 /**
161 * Utilities for making it easy to test React components.
162 *
@@ -391,57 +393,24 @@ const ReactTestUtils = {
393 Simulate: null,
394 SimulateNative: {},
395
394 - act(callback: () => void): Thenable {
396 + act(callback: () => Thenable) {
397 if (actContainerElement === null) {
396 - // warn if we can't actually create the stub element
398 if (__DEV__) {
398 - warningWithoutStack(
399 - typeof document !== 'undefined' &&
400 - document !== null &&
401 - typeof document.createElement === 'function',
402 - 'It looks like you called TestUtils.act(...) in a non-browser environment. ' +
403 - "If you're using TestRenderer for your tests, you should call " +
404 - 'TestRenderer.act(...) instead of TestUtils.act(...).',
405 - );
406 - }
407 - // then make it
408 - actContainerElement = document.createElement('div');
409 - }
410 -
411 - const result = ReactDOM.unstable_batchedUpdates(callback);
412 - // note: keep these warning messages in sync with
413 - // createReactNoop.js and ReactTestRenderer.js
414 - if (__DEV__) {
415 - if (result !== undefined) {
416 - let addendum;
417 - if (result !== null && typeof result.then === 'function') {
418 - addendum =
419 - '\n\nIt looks like you wrote ReactTestUtils.act(async () => ...), ' +
420 - 'or returned a Promise from the callback passed to it. ' +
421 - 'Putting asynchronous logic inside ReactTestUtils.act(...) is not supported.\n';
422 - } else {
423 - addendum = ' You returned: ' + result;
424 - }
425 - warningWithoutStack(
426 - false,
427 - 'The callback passed to ReactTestUtils.act(...) function must not return anything.%s',
428 - addendum,
429 - );
430 - }
431 - }
432 - ReactDOM.render(<div />, actContainerElement);
433 - // we want the user to not expect a return,
434 - // but we want to warn if they use it like they can await on it.
435 - return {
436 - then() {
437 - if (__DEV__) {
399 + // warn if we're trying to use this in something like node (without jsdom)
400 + if (didWarnAboutActInNodejs === false) {
401 + didWarnAboutActInNodejs = true;
402 warningWithoutStack(
439 - false,
440 - 'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
403 + typeof document !== 'undefined' && document !== null,
404 + 'It looks like you called ReactTestUtils.act(...) in a non-browser environment. ' +
405 + "If you're using TestRenderer for your tests, you should call " +
406 + 'ReactTestRenderer.act(...) instead of ReactTestUtils.act(...).',
407 );
408 }
443 - },
444 - };
409 + }
410 + // now make the stub element
411 + actContainerElement = document.createElement('div');
412 + }
413 + return act(callback);
414 },
415 };
416
packages/react-dom/src/test-utils/ReactTestUtilsAct.js new
+172
@@ -0,0 +1,172 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
11 +
12 +import warningWithoutStack from 'shared/warningWithoutStack';
13 +import ReactDOM from 'react-dom';
14 +import ReactSharedInternals from 'shared/ReactSharedInternals';
15 +import enqueueTask from 'shared/enqueueTask';
16 +
17 +// Keep in sync with ReactDOMUnstableNativeDependencies.js
18 +// ReactDOM.js, and ReactTestUtils.js:
19 +const [
20 + /* eslint-disable no-unused-vars */
21 + getInstanceFromNode,
22 + getNodeFromInstance,
23 + getFiberCurrentPropsFromNode,
24 + injectEventPluginsByName,
25 + eventNameDispatchConfigs,
26 + accumulateTwoPhaseDispatches,
27 + accumulateDirectDispatches,
28 + enqueueStateRestore,
29 + restoreStateIfNeeded,
30 + dispatchEvent,
31 + runEventsInBatch,
32 + /* eslint-enable no-unused-vars */
33 + flushPassiveEffects,
34 +] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
35 +
36 +const batchedUpdates = ReactDOM.unstable_batchedUpdates;
37 +
38 +const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
39 +
40 +// this implementation should be exactly the same in
41 +// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
42 +
43 +// we track the 'depth' of the act() calls with this counter,
44 +// so we can tell if any async act() calls try to run in parallel.
45 +let actingUpdatesScopeDepth = 0;
46 +
47 +function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
48 + try {
49 + flushPassiveEffects();
50 + enqueueTask(() => {
51 + if (flushPassiveEffects()) {
52 + flushEffectsAndMicroTasks(onDone);
53 + } else {
54 + onDone();
55 + }
56 + });
57 + } catch (err) {
58 + onDone(err);
59 + }
60 +}
61 +
62 +function act(callback: () => Thenable) {
63 + let previousActingUpdatesScopeDepth;
64 + if (__DEV__) {
65 + previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
66 + actingUpdatesScopeDepth++;
67 + ReactShouldWarnActingUpdates.current = true;
68 + }
69 +
70 + function onDone() {
71 + if (__DEV__) {
72 + actingUpdatesScopeDepth--;
73 + if (actingUpdatesScopeDepth === 0) {
74 + ReactShouldWarnActingUpdates.current = false;
75 + }
76 + if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
77 + // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
78 + warningWithoutStack(
79 + null,
80 + 'You seem to have overlapping act() calls, this is not supported. ' +
81 + 'Be sure to await previous act() calls before making a new one. ',
82 + );
83 + }
84 + }
85 + }
86 +
87 + const result = batchedUpdates(callback);
88 + if (
89 + result !== null &&
90 + typeof result === 'object' &&
91 + typeof result.then === 'function'
92 + ) {
93 + // setup a boolean that gets set to true only
94 + // once this act() call is await-ed
95 + let called = false;
96 + if (__DEV__) {
97 + if (typeof Promise !== 'undefined') {
98 + //eslint-disable-next-line no-undef
99 + Promise.resolve()
100 + .then(() => {})
101 + .then(() => {
102 + if (called === false) {
103 + warningWithoutStack(
104 + null,
105 + 'You called act(async () => ...) without await. ' +
106 + 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
107 + 'calls and mixing their scopes. You should - await act(async () => ...);',
108 + );
109 + }
110 + });
111 + }
112 + }
113 +
114 + // in the async case, the returned thenable runs the callback, flushes
115 + // effects and microtasks in a loop until flushPassiveEffects() === false,
116 + // and cleans up
117 + return {
118 + then(resolve: () => void, reject: (?Error) => void) {
119 + called = true;
120 + result.then(
121 + () => {
122 + flushEffectsAndMicroTasks((err: ?Error) => {
123 + onDone();
124 + if (err) {
125 + reject(err);
126 + } else {
127 + resolve();
128 + }
129 + });
130 + },
131 + err => {
132 + onDone();
133 + reject(err);
134 + },
135 + );
136 + },
137 + };
138 + } else {
139 + if (__DEV__) {
140 + warningWithoutStack(
141 + result === undefined,
142 + 'The callback passed to act(...) function ' +
143 + 'must return undefined, or a Promise. You returned %s',
144 + result,
145 + );
146 + }
147 +
148 + // flush effects until none remain, and cleanup
149 + try {
150 + while (flushPassiveEffects()) {}
151 + onDone();
152 + } catch (err) {
153 + onDone();
154 + throw err;
155 + }
156 +
157 + // in the sync case, the returned thenable only warns *if* await-ed
158 + return {
159 + then(resolve: () => void) {
160 + if (__DEV__) {
161 + warningWithoutStack(
162 + false,
163 + 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
164 + );
165 + }
166 + resolve();
167 + },
168 + };
169 + }
170 +}
171 +
172 +export default act;
packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
+1 -1
@@ -11,7 +11,7 @@ import ResponderEventPlugin from 'events/ResponderEventPlugin';
11 import ResponderTouchHistoryStore from 'events/ResponderTouchHistoryStore';
12
13 // Inject react-dom's ComponentTree into this module.
14 -// Keep in sync with ReactDOM.js and ReactTestUtils.js:
14 +// Keep in sync with ReactDOM.js, ReactTestUtils.js, and ReactTestUtilsAct.js:
15 const [
16 getInstanceFromNode,
17 getNodeFromInstance,
packages/react-noop-renderer/src/createReactNoop.js
+143 -53
@@ -14,6 +14,7 @@
14 * environment.
15 */
16
17 +import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
18 import type {Fiber} from 'react-reconciler/src/ReactFiber';
19 import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
20 import type {ReactNodeList} from 'shared/ReactTypes';
@@ -26,16 +27,12 @@ import {
27 REACT_ELEMENT_TYPE,
28 REACT_EVENT_TARGET_TOUCH_HIT,
29 } from 'shared/ReactSymbols';
29 -import warningWithoutStack from 'shared/warningWithoutStack';
30 import warning from 'shared/warning';
31 -
31 +import enqueueTask from 'shared/enqueueTask';
32 +import ReactSharedInternals from 'shared/ReactSharedInternals';
33 +import warningWithoutStack from 'shared/warningWithoutStack';
34 import {enableEventAPI} from 'shared/ReactFeatureFlags';
35
34 -// for .act's return value
35 -type Thenable = {
36 - then(resolve: () => mixed, reject?: () => mixed): mixed,
37 -};
38 -
36 type Container = {
37 rootID: string,
38 children: Array<Instance | TextInstance>,
@@ -59,6 +56,8 @@ type TextInstance = {|
56 |};
57 type HostContext = Object;
58
59 +const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
60 +
61 const NO_CONTEXT = {};
62 const UPPERCASE_CONTEXT = {};
63 const EVENT_COMPONENT_CONTEXT = {};
@@ -598,6 +597,140 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
597 const roots = new Map();
598 const DEFAULT_ROOT_ID = '<default>';
599
600 + const {flushPassiveEffects, batchedUpdates} = NoopRenderer;
601 +
602 + // this act() implementation should be exactly the same in
603 + // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
604 +
605 + let actingUpdatesScopeDepth = 0;
606 +
607 + function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
608 + try {
609 + flushPassiveEffects();
610 + enqueueTask(() => {
611 + if (flushPassiveEffects()) {
612 + flushEffectsAndMicroTasks(onDone);
613 + } else {
614 + onDone();
615 + }
616 + });
617 + } catch (err) {
618 + onDone(err);
619 + }
620 + }
621 +
622 + function act(callback: () => Thenable) {
623 + let previousActingUpdatesScopeDepth;
624 + if (__DEV__) {
625 + previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
626 + actingUpdatesScopeDepth++;
627 + ReactShouldWarnActingUpdates.current = true;
628 + }
629 +
630 + function onDone() {
631 + if (__DEV__) {
632 + actingUpdatesScopeDepth--;
633 + if (actingUpdatesScopeDepth === 0) {
634 + ReactShouldWarnActingUpdates.current = false;
635 + }
636 + if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
637 + // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
638 + warningWithoutStack(
639 + null,
640 + 'You seem to have overlapping act() calls, this is not supported. ' +
641 + 'Be sure to await previous act() calls before making a new one. ',
642 + );
643 + }
644 + }
645 + }
646 +
647 + const result = batchedUpdates(callback);
648 + if (
649 + result !== null &&
650 + typeof result === 'object' &&
651 + typeof result.then === 'function'
652 + ) {
653 + // setup a boolean that gets set to true only
654 + // once this act() call is await-ed
655 + let called = false;
656 + if (__DEV__) {
657 + if (typeof Promise !== 'undefined') {
658 + //eslint-disable-next-line no-undef
659 + Promise.resolve()
660 + .then(() => {})
661 + .then(() => {
662 + if (called === false) {
663 + warningWithoutStack(
664 + null,
665 + 'You called act(async () => ...) without await. ' +
666 + 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
667 + 'calls and mixing their scopes. You should - await act(async () => ...);',
668 + );
669 + }
670 + });
671 + }
672 + }
673 +
674 + // in the async case, the returned thenable runs the callback, flushes
675 + // effects and microtasks in a loop until flushPassiveEffects() === false,
676 + // and cleans up
677 + return {
678 + then(resolve: () => void, reject: (?Error) => void) {
679 + called = true;
680 + result.then(
681 + () => {
682 + flushEffectsAndMicroTasks((err: ?Error) => {
683 + onDone();
684 + if (err) {
685 + reject(err);
686 + } else {
687 + resolve();
688 + }
689 + });
690 + },
691 + err => {
692 + onDone();
693 + reject(err);
694 + },
695 + );
696 + },
697 + };
698 + } else {
699 + if (__DEV__) {
700 + warningWithoutStack(
701 + result === undefined,
702 + 'The callback passed to act(...) function ' +
703 + 'must return undefined, or a Promise. You returned %s',
704 + result,
705 + );
706 + }
707 +
708 + // flush effects until none remain, and cleanup
709 + try {
710 + while (flushPassiveEffects()) {}
711 + onDone();
712 + } catch (err) {
713 + onDone();
714 + throw err;
715 + }
716 +
717 + // in the sync case, the returned thenable only warns *if* await-ed
718 + return {
719 + then(resolve: () => void) {
720 + if (__DEV__) {
721 + warningWithoutStack(
722 + false,
723 + 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
724 + );
725 + }
726 + resolve();
727 + },
728 + };
729 + }
730 + }
731 +
732 + // end act() implementation
733 +
734 function childToJSX(child, text) {
735 if (text !== null) {
736 return text;
@@ -843,56 +976,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
976
977 interactiveUpdates: NoopRenderer.interactiveUpdates,
978
846 - // maybe this should exist only in the test file
847 - act(callback: () => void): Thenable {
848 - // note: keep these warning messages in sync with
849 - // ReactTestRenderer.js and ReactTestUtils.js
850 - let result = NoopRenderer.batchedUpdates(callback);
851 - if (__DEV__) {
852 - if (result !== undefined) {
853 - let addendum;
854 - if (result !== null && typeof result.then === 'function') {
855 - addendum =
856 - "\n\nIt looks like you wrote ReactNoop.act(async () => ...) or returned a Promise from it's callback. " +
857 - 'Putting asynchronous logic inside ReactNoop.act(...) is not supported.\n';
858 - } else {
859 - addendum = ' You returned: ' + result;
860 - }
861 - warningWithoutStack(
862 - false,
863 - 'The callback passed to ReactNoop.act(...) function must not return anything.%s',
864 - addendum,
865 - );
866 - }
867 - }
868 - ReactNoop.flushPassiveEffects();
869 - // we want the user to not expect a return,
870 - // but we want to warn if they use it like they can await on it.
871 - return {
872 - then() {
873 - if (__DEV__) {
874 - warningWithoutStack(
875 - false,
876 - 'Do not await the result of calling ReactNoop.act(...), it is not a Promise.',
877 - );
878 - }
879 - },
880 - };
881 - },
882 -
979 flushSync(fn: () => mixed) {
980 NoopRenderer.flushSync(fn);
981 },
982
887 - flushPassiveEffects() {
888 - // Trick to flush passive effects without exposing an internal API:
889 - // Create a throwaway root and schedule a dummy update on it.
890 - const rootID = 'bloopandthenmoreletterstoavoidaconflict';
891 - const container = {rootID: rootID, pendingChildren: [], children: []};
892 - rootContainers.set(rootID, container);
893 - const root = NoopRenderer.createContainer(container, true, false);
894 - NoopRenderer.updateContainer(null, root, null, null);
895 - },
983 + flushPassiveEffects: NoopRenderer.flushPassiveEffects,
984 +
985 + act,
986
987 // Logs the current state of the tree.
988 dumpTree(rootID: string = DEFAULT_ROOT_ID) {
packages/react-reconciler/src/ReactFiberHooks.js
+6 -16
@@ -30,10 +30,10 @@ import {
30 } from './ReactHookEffectTags';
31 import {
32 scheduleWork,
33 - warnIfNotCurrentlyBatchingInDev,
33 computeExpirationForFiber,
34 flushPassiveEffects,
35 requestCurrentTime,
36 + warnIfNotCurrentlyActingUpdatesInDev,
37 } from './ReactFiberScheduler';
38
39 import invariant from 'shared/invariant';
@@ -1046,19 +1046,6 @@ function updateMemo<T>(
1046 return nextValue;
1047 }
1048
1049 -// in a test-like environment, we want to warn if dispatchAction()
1050 -// is called outside of a batchedUpdates/TestUtils.act(...) call.
1051 -let shouldWarnForUnbatchedSetState = false;
1052 -
1053 -if (__DEV__) {
1054 - // jest isn't a 'global', it's just exposed to tests via a wrapped function
1055 - // further, this isn't a test file, so flow doesn't recognize the symbol. So...
1056 - // $FlowExpectedError - because requirements don't give a damn about your type sigs.
1057 - if ('undefined' !== typeof jest) {
1058 - shouldWarnForUnbatchedSetState = true;
1059 - }
1060 -}
1061 -
1049 function dispatchAction<S, A>(
1050 fiber: Fiber,
1051 queue: UpdateQueue<S, A>,
@@ -1178,8 +1165,11 @@ function dispatchAction<S, A>(
1165 }
1166 }
1167 if (__DEV__) {
1181 - if (shouldWarnForUnbatchedSetState === true) {
1182 - warnIfNotCurrentlyBatchingInDev(fiber);
1168 + // jest isn't a 'global', it's just exposed to tests via a wrapped function
1169 + // further, this isn't a test file, so flow doesn't recognize the symbol. So...
1170 + // $FlowExpectedError - because requirements don't give a damn about your type sigs.
1171 + if ('undefined' !== typeof jest) {
1172 + warnIfNotCurrentlyActingUpdatesInDev(fiber);
1173 }
1174 }
1175 scheduleWork(fiber, expirationTime);
packages/react-reconciler/src/ReactFiberReconciler.js
+1
@@ -310,6 +310,7 @@ export {
310 flushInteractiveUpdates,
311 flushControlled,
312 flushSync,
313 + flushPassiveEffects,
314 };
315
316 export function getPublicRootInstance(
packages/react-reconciler/src/ReactFiberScheduler.js
+5 -5
@@ -34,7 +34,7 @@ import {
34 flushInteractiveUpdates as flushInteractiveUpdates_old,
35 computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
36 flushPassiveEffects as flushPassiveEffects_old,
37 - warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_old,
37 + warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
38 } from './ReactFiberScheduler.old';
39
40 import {
@@ -62,7 +62,7 @@ import {
62 flushInteractiveUpdates as flushInteractiveUpdates_new,
63 computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
64 flushPassiveEffects as flushPassiveEffects_new,
65 - warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_new,
65 + warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
66 } from './ReactFiberScheduler.new';
67
68 export let requestCurrentTime = requestCurrentTime_old;
@@ -89,7 +89,7 @@ export let interactiveUpdates = interactiveUpdates_old;
89 export let flushInteractiveUpdates = flushInteractiveUpdates_old;
90 export let computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_old;
91 export let flushPassiveEffects = flushPassiveEffects_old;
92 -export let warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_old;
92 +export let warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_old;
93
94 if (enableNewScheduler) {
95 requestCurrentTime = requestCurrentTime_new;
@@ -116,9 +116,9 @@ if (enableNewScheduler) {
116 flushInteractiveUpdates = flushInteractiveUpdates_new;
117 computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_new;
118 flushPassiveEffects = flushPassiveEffects_new;
119 - warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_new;
119 + warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_new;
120 }
121
122 export type Thenable = {
123 - then(resolve: () => mixed, reject?: () => mixed): mixed,
123 + then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
124 };
packages/react-reconciler/src/ReactFiberScheduler.new.js
+1 -1
@@ -33,4 +33,4 @@ export const interactiveUpdates = notYetImplemented;
33 export const flushInteractiveUpdates = notYetImplemented;
34 export const computeUniqueAsyncExpiration = notYetImplemented;
35 export const flushPassiveEffects = notYetImplemented;
36 -export const warnIfNotCurrentlyBatchingInDev = notYetImplemented;
36 +export const warnIfNotCurrentlyActingUpdatesInDev = notYetImplemented;
packages/react-reconciler/src/ReactFiberScheduler.old.js
+21 -4
@@ -176,10 +176,14 @@ const {
176 } = Scheduler;
177
178 export type Thenable = {
179 - then(resolve: () => mixed, reject?: () => mixed): mixed,
179 + then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
180 };
181
182 -const {ReactCurrentDispatcher, ReactCurrentOwner} = ReactSharedInternals;
182 +const {
183 + ReactCurrentDispatcher,
184 + ReactCurrentOwner,
185 + ReactShouldWarnActingUpdates,
186 +} = ReactSharedInternals;
187
188 let didWarnAboutStateTransition;
189 let didWarnSetStateChildContext;
@@ -610,6 +614,7 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
614 }
615
616 function flushPassiveEffects() {
617 + const didFlushEffects = passiveEffectCallback !== null;
618 if (passiveEffectCallbackHandle !== null) {
619 cancelCallback(passiveEffectCallbackHandle);
620 }
@@ -618,6 +623,7 @@ function flushPassiveEffects() {
623 // to ensure tracing works correctly.
624 passiveEffectCallback();
625 }
626 + return didFlushEffects;
627 }
628
629 function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
@@ -1836,9 +1842,20 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
1842 return root;
1843 }
1844
1839 -export function warnIfNotCurrentlyBatchingInDev(fiber: Fiber): void {
1845 +// in a test-like environment, we want to warn if dispatchAction() is
1846 +// called outside of a TestUtils.act(...)/batchedUpdates/render call.
1847 +// so we have a a step counter for when we descend/ascend from
1848 +// act() calls, and test on it for when to warn
1849 +// It's a tuple with a single value. Look for shared/createAct to
1850 +// see how we change the value inside act() calls
1851 +
1852 +export function warnIfNotCurrentlyActingUpdatesInDev(fiber: Fiber): void {
1853 if (__DEV__) {
1841 - if (isRendering === false && isBatchingUpdates === false) {
1854 + if (
1855 + isBatchingUpdates === false &&
1856 + isRendering === false &&
1857 + ReactShouldWarnActingUpdates.current === false
1858 + ) {
1859 warningWithoutStack(
1860 false,
1861 'An update to %s inside a test was not wrapped in act(...).\n\n' +
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+4 -11
@@ -1046,11 +1046,10 @@ describe('ReactHooks', () => {
1046
1047 class Cls extends React.Component {
1048 render() {
1049 - act(() =>
1050 - _setState(() => {
1051 - ReactCurrentDispatcher.current.readContext(ThemeContext);
1052 - }),
1049 + _setState(() =>
1050 + ReactCurrentDispatcher.current.readContext(ThemeContext),
1051 );
1052 +
1053 return null;
1054 }
1055 }
@@ -1062,13 +1061,7 @@ describe('ReactHooks', () => {
1061 <Cls />
1062 </React.Fragment>,
1063 ),
1065 - ).toWarnDev(
1066 - [
1067 - 'Context can only be read while React is rendering',
1068 - 'Render methods should be a pure function of props and state',
1069 - ],
1070 - {withoutStack: 1},
1071 - );
1064 + ).toWarnDev(['Context can only be read while React is rendering']);
1065 });
1066
1067 it('warns when calling hooks inside useReducer', () => {
packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js new
+63
@@ -0,0 +1,63 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @jest-environment node
8 + */
9 +
10 +// sanity tests for ReactNoop.act()
11 +
12 +jest.useRealTimers();
13 +const React = require('react');
14 +const ReactNoop = require('react-noop-renderer');
15 +const Scheduler = require('scheduler');
16 +
17 +describe('ReactNoop.act()', () => {
18 + it('can use act to flush effects', async () => {
19 + function App(props) {
20 + React.useEffect(props.callback);
21 + return null;
22 + }
23 +
24 + let calledLog = [];
25 + ReactNoop.act(() => {
26 + ReactNoop.render(
27 + <App
28 + callback={() => {
29 + calledLog.push(calledLog.length);
30 + }}
31 + />,
32 + );
33 + });
34 + expect(Scheduler).toFlushWithoutYielding();
35 + expect(calledLog).toEqual([0]);
36 + });
37 +
38 + it('should work with async/await', async () => {
39 + function App() {
40 + let [ctr, setCtr] = React.useState(0);
41 + async function someAsyncFunction() {
42 + Scheduler.yieldValue('stage 1');
43 + await null;
44 + Scheduler.yieldValue('stage 2');
45 + setCtr(1);
46 + }
47 + React.useEffect(() => {
48 + someAsyncFunction();
49 + }, []);
50 + return ctr;
51 + }
52 + await ReactNoop.act(async () => {
53 + ReactNoop.act(() => {
54 + ReactNoop.render(<App />);
55 + });
56 + await null;
57 + expect(Scheduler).toFlushAndYield(['stage 1']);
58 + });
59 + expect(Scheduler).toHaveYielded(['stage 2']);
60 + expect(Scheduler).toFlushWithoutYielding();
61 + expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]);
62 + });
63 +});
packages/react-test-renderer/src/ReactTestRenderer.js
+4 -61
@@ -40,7 +40,7 @@ import {
40 } from 'shared/ReactWorkTags';
41 import invariant from 'shared/invariant';
42 import ReactVersion from 'shared/ReactVersion';
43 -import warningWithoutStack from 'shared/warningWithoutStack';
43 +import act from './ReactTestRendererAct';
44
45 import {getPublicInstance} from './ReactTestHostConfig';
46
@@ -65,11 +65,6 @@ type FindOptions = $Shape<{
65
66 export type Predicate = (node: ReactTestInstance) => ?boolean;
67
68 -// for .act's return value
69 -type Thenable = {
70 - then(resolve: () => mixed, reject?: () => mixed): mixed,
71 -};
72 -
68 const defaultTestOptions = {
69 createNodeMock: function() {
70 return null;
@@ -549,63 +544,11 @@ const ReactTestRendererFiber = {
544 return entry;
545 },
546
552 - /* eslint-disable camelcase */
547 + /* eslint-disable-next-line camelcase */
548 unstable_batchedUpdates: batchedUpdates,
554 - /* eslint-enable camelcase */
555 -
556 - act(callback: () => void): Thenable {
557 - // note: keep these warning messages in sync with
558 - // createNoop.js and ReactTestUtils.js
559 - let result = batchedUpdates(callback);
560 - if (__DEV__) {
561 - if (result !== undefined) {
562 - let addendum;
563 - if (result !== null && typeof result.then === 'function') {
564 - addendum =
565 - "\n\nIt looks like you wrote TestRenderer.act(async () => ...) or returned a Promise from it's callback. " +
566 - 'Putting asynchronous logic inside TestRenderer.act(...) is not supported.\n';
567 - } else {
568 - addendum = ' You returned: ' + result;
569 - }
570 - warningWithoutStack(
571 - false,
572 - 'The callback passed to TestRenderer.act(...) function must not return anything.%s',
573 - addendum,
574 - );
575 - }
576 - }
577 - flushPassiveEffects();
578 - // we want the user to not expect a return,
579 - // but we want to warn if they use it like they can await on it.
580 - return {
581 - then() {
582 - if (__DEV__) {
583 - warningWithoutStack(
584 - false,
585 - 'Do not await the result of calling TestRenderer.act(...), it is not a Promise.',
586 - );
587 - }
588 - },
589 - };
590 - },
591 -};
549
593 -// root used to flush effects during .act() calls
594 -const actRoot = createContainer(
595 - {
596 - children: [],
597 - createNodeMock: defaultTestOptions.createNodeMock,
598 - tag: 'CONTAINER',
599 - },
600 - true,
601 - false,
602 -);
603 -
604 -function flushPassiveEffects() {
605 - // Trick to flush passive effects without exposing an internal API:
606 - // Create a throwaway root and schedule a dummy update on it.
607 - updateContainer(null, actRoot, null, null);
608 -}
550 + act,
551 +};
552
553 const fiberToWrapper = new WeakMap();
554 function wrapFiber(fiber: Fiber): ReactTestInstance {
packages/react-test-renderer/src/ReactTestRendererAct.js new
+153
@@ -0,0 +1,153 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
10 +
11 +import {
12 + batchedUpdates,
13 + flushPassiveEffects,
14 +} from 'react-reconciler/inline.test';
15 +import ReactSharedInternals from 'shared/ReactSharedInternals';
16 +import warningWithoutStack from 'shared/warningWithoutStack';
17 +import enqueueTask from 'shared/enqueueTask';
18 +
19 +const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
20 +
21 +// this implementation should be exactly the same in
22 +// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
23 +
24 +// we track the 'depth' of the act() calls with this counter,
25 +// so we can tell if any async act() calls try to run in parallel.
26 +let actingUpdatesScopeDepth = 0;
27 +
28 +function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
29 + try {
30 + flushPassiveEffects();
31 + enqueueTask(() => {
32 + if (flushPassiveEffects()) {
33 + flushEffectsAndMicroTasks(onDone);
34 + } else {
35 + onDone();
36 + }
37 + });
38 + } catch (err) {
39 + onDone(err);
40 + }
41 +}
42 +
43 +function act(callback: () => Thenable) {
44 + let previousActingUpdatesScopeDepth;
45 + if (__DEV__) {
46 + previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
47 + actingUpdatesScopeDepth++;
48 + ReactShouldWarnActingUpdates.current = true;
49 + }
50 +
51 + function onDone() {
52 + if (__DEV__) {
53 + actingUpdatesScopeDepth--;
54 + if (actingUpdatesScopeDepth === 0) {
55 + ReactShouldWarnActingUpdates.current = false;
56 + }
57 + if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
58 + // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
59 + warningWithoutStack(
60 + null,
61 + 'You seem to have overlapping act() calls, this is not supported. ' +
62 + 'Be sure to await previous act() calls before making a new one. ',
63 + );
64 + }
65 + }
66 + }
67 +
68 + const result = batchedUpdates(callback);
69 + if (
70 + result !== null &&
71 + typeof result === 'object' &&
72 + typeof result.then === 'function'
73 + ) {
74 + // setup a boolean that gets set to true only
75 + // once this act() call is await-ed
76 + let called = false;
77 + if (__DEV__) {
78 + if (typeof Promise !== 'undefined') {
79 + //eslint-disable-next-line no-undef
80 + Promise.resolve()
81 + .then(() => {})
82 + .then(() => {
83 + if (called === false) {
84 + warningWithoutStack(
85 + null,
86 + 'You called act(async () => ...) without await. ' +
87 + 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
88 + 'calls and mixing their scopes. You should - await act(async () => ...);',
89 + );
90 + }
91 + });
92 + }
93 + }
94 +
95 + // in the async case, the returned thenable runs the callback, flushes
96 + // effects and microtasks in a loop until flushPassiveEffects() === false,
97 + // and cleans up
98 + return {
99 + then(resolve: () => void, reject: (?Error) => void) {
100 + called = true;
101 + result.then(
102 + () => {
103 + flushEffectsAndMicroTasks((err: ?Error) => {
104 + onDone();
105 + if (err) {
106 + reject(err);
107 + } else {
108 + resolve();
109 + }
110 + });
111 + },
112 + err => {
113 + onDone();
114 + reject(err);
115 + },
116 + );
117 + },
118 + };
119 + } else {
120 + if (__DEV__) {
121 + warningWithoutStack(
122 + result === undefined,
123 + 'The callback passed to act(...) function ' +
124 + 'must return undefined, or a Promise. You returned %s',
125 + result,
126 + );
127 + }
128 +
129 + // flush effects until none remain, and cleanup
130 + try {
131 + while (flushPassiveEffects()) {}
132 + onDone();
133 + } catch (err) {
134 + onDone();
135 + throw err;
136 + }
137 +
138 + // in the sync case, the returned thenable only warns *if* await-ed
139 + return {
140 + then(resolve: () => void) {
141 + if (__DEV__) {
142 + warningWithoutStack(
143 + false,
144 + 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
145 + );
146 + }
147 + resolve();
148 + },
149 + };
150 + }
151 +}
152 +
153 +export default act;
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
+14 -35
@@ -1023,40 +1023,19 @@ describe('ReactTestRenderer', () => {
1023 ReactTestRenderer.create(<App />);
1024 });
1025
1026 - describe('act', () => {
1027 - it('can use .act() to batch updates and effects', () => {
1028 - function App(props) {
1029 - React.useEffect(() => {
1030 - props.callback();
1031 - });
1032 - return null;
1033 - }
1034 - let called = false;
1035 - ReactTestRenderer.act(() => {
1036 - ReactTestRenderer.create(
1037 - <App
1038 - callback={() => {
1039 - called = true;
1040 - }}
1041 - />,
1042 - );
1043 - });
1044 -
1045 - expect(called).toBe(true);
1046 - });
1047 - it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
1048 - // we warn when you try to load 2 renderers in the same 'scope'
1049 - // so as suggested, we call resetModules() to carry on with the test
1050 - jest.resetModules();
1051 - const {act} = require('react-dom/test-utils');
1052 - expect(() => {
1053 - expect(() => act(() => {})).toThrow('document is not defined');
1054 - }).toWarnDev(
1055 - [
1056 - 'It looks like you called TestUtils.act(...) in a non-browser environment',
1057 - ],
1058 - {withoutStack: 1},
1059 - );
1060 - });
1026 + // we run this test here because we need a dom-less scope
1027 + it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
1028 + // we warn when you try to load 2 renderers in the same 'scope'
1029 + // so as suggested, we call resetModules() to carry on with the test
1030 + jest.resetModules();
1031 + const {act} = require('react-dom/test-utils');
1032 + expect(() => {
1033 + expect(() => act(() => {})).toThrow('document is not defined');
1034 + }).toWarnDev(
1035 + [
1036 + 'It looks like you called ReactTestUtils.act(...) in a non-browser environment',
1037 + ],
1038 + {withoutStack: 1},
1039 + );
1040 });
1041 });
packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js new
+122
@@ -0,0 +1,122 @@
1 +jest.useRealTimers();
2 +
3 +let React;
4 +let ReactTestRenderer;
5 +let Scheduler;
6 +let act;
7 +
8 +describe('ReactTestRenderer.act()', () => {
9 + beforeEach(() => {
10 + jest.resetModules();
11 + React = require('react');
12 + ReactTestRenderer = require('react-test-renderer');
13 + Scheduler = require('scheduler');
14 + act = ReactTestRenderer.act;
15 + });
16 + it('can use .act() to flush effects', () => {
17 + function App(props) {
18 + let [ctr, setCtr] = React.useState(0);
19 + React.useEffect(() => {
20 + props.callback();
21 + setCtr(1);
22 + }, []);
23 + return ctr;
24 + }
25 + let calledLog = [];
26 + let root;
27 + act(() => {
28 + root = ReactTestRenderer.create(
29 + <App
30 + callback={() => {
31 + calledLog.push(calledLog.length);
32 + }}
33 + />,
34 + );
35 + });
36 +
37 + expect(calledLog).toEqual([0]);
38 + expect(root.toJSON()).toEqual('1');
39 + });
40 +
41 + it("warns if you don't use .act", () => {
42 + let setCtr;
43 + function App(props) {
44 + let [ctr, _setCtr] = React.useState(0);
45 + setCtr = _setCtr;
46 + return ctr;
47 + }
48 +
49 + ReactTestRenderer.create(<App />);
50 +
51 + expect(() => {
52 + setCtr(1);
53 + }).toWarnDev([
54 + 'An update to App inside a test was not wrapped in act(...)',
55 + ]);
56 + });
57 +
58 + describe('async', () => {
59 + it('should work with async/await', async () => {
60 + function fetch(url) {
61 + return Promise.resolve({
62 + details: [1, 2, 3],
63 + });
64 + }
65 + function App() {
66 + let [details, setDetails] = React.useState(0);
67 +
68 + React.useEffect(() => {
69 + async function fetchDetails() {
70 + const response = await fetch();
71 + setDetails(response.details);
72 + }
73 + fetchDetails();
74 + }, []);
75 + return details;
76 + }
77 + let root;
78 +
79 + await ReactTestRenderer.act(async () => {
80 + root = ReactTestRenderer.create(<App />);
81 + });
82 +
83 + expect(root.toJSON()).toEqual(['1', '2', '3']);
84 + });
85 +
86 + it('should not flush effects without also flushing microtasks', async () => {
87 + const {useEffect, useReducer} = React;
88 +
89 + const alreadyResolvedPromise = Promise.resolve();
90 +
91 + function App() {
92 + // This component will keep updating itself until step === 3
93 + const [step, proceed] = useReducer(s => (s === 3 ? 3 : s + 1), 1);
94 + useEffect(() => {
95 + Scheduler.yieldValue('Effect');
96 + alreadyResolvedPromise.then(() => {
97 + Scheduler.yieldValue('Microtask');
98 + proceed();
99 + });
100 + });
101 + return step;
102 + }
103 + const root = ReactTestRenderer.create(null);
104 + await act(async () => {
105 + root.update(<App />);
106 + });
107 + expect(Scheduler).toHaveYielded([
108 + // Should not flush effects without also flushing microtasks
109 + // First render:
110 + 'Effect',
111 + 'Microtask',
112 + // Second render:
113 + 'Effect',
114 + 'Microtask',
115 + // Final render:
116 + 'Effect',
117 + 'Microtask',
118 + ]);
119 + expect(root).toMatchRenderedOutput('3');
120 + });
121 + });
122 +});
packages/react/src/ReactSharedInternals.js
+2
@@ -15,6 +15,8 @@ import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
15 const ReactSharedInternals = {
16 ReactCurrentDispatcher,
17 ReactCurrentOwner,
18 + // used by act()
19 + ReactShouldWarnActingUpdates: {current: false},
20 // Used by renderers to avoid bundling object-assign twice in UMD bundles:
21 assign,
22 };
packages/shared/enqueueTask.js new
+42
@@ -0,0 +1,42 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import warningWithoutStack from './warningWithoutStack';
11 +
12 +let didWarnAboutMessageChannel = false;
13 +let enqueueTask;
14 +try {
15 + // assuming we're in node, let's try to get node's
16 + // version of setImmediate, bypassing fake timers if any
17 + let r = require; // trick packagers not to bundle this stuff.
18 + enqueueTask = r('timers').setImmediate;
19 +} catch (_err) {
20 + // we're in a browser
21 + // we can't use regular timers because they may still be faked
22 + // so we try MessageChannel+postMessage instead
23 + enqueueTask = function(callback: () => void) {
24 + if (__DEV__) {
25 + if (didWarnAboutMessageChannel === false) {
26 + didWarnAboutMessageChannel = true;
27 + warningWithoutStack(
28 + typeof MessageChannel !== 'undefined',
29 + 'This browser does not have a MessageChannel implementation, ' +
30 + 'so enqueuing tasks via await act(async () => ...) will fail. ' +
31 + 'Please file an issue at https://github.com/facebook/react/issues ' +
32 + 'if you encounter this warning.',
33 + );
34 + }
35 + }
36 + const channel = new MessageChannel();
37 + channel.port1.onmessage = callback;
38 + channel.port2.postMessage(undefined);
39 + };
40 +}
41 +
42 +export default enqueueTask;
scripts/rollup/results.json
+411 -187
@@ -4,29 +4,29 @@
4 "filename": "react.development.js",
5 "bundleType": "UMD_DEV",
6 "packageName": "react",
7 - "size": 102492,
8 - "gzip": 26625
7 + "size": 103860,
8 + "gzip": 26948
9 },
10 {
11 "filename": "react.production.min.js",
12 "bundleType": "UMD_PROD",
13 "packageName": "react",
14 - "size": 12609,
15 - "gzip": 4834
14 + "size": 12346,
15 + "gzip": 4735
16 },
17 {
18 "filename": "react.development.js",
19 "bundleType": "NODE_DEV",
20 "packageName": "react",
21 - "size": 64139,
22 - "gzip": 17318
21 + "size": 65507,
22 + "gzip": 17628
23 },
24 {
25 "filename": "react.production.min.js",
26 "bundleType": "NODE_PROD",
27 "packageName": "react",
28 - "size": 6834,
29 - "gzip": 2814
28 + "size": 6571,
29 + "gzip": 2718
30 },
31 {
32 "filename": "React-dev.js",
@@ -46,29 +46,29 @@
46 "filename": "react-dom.development.js",
47 "bundleType": "UMD_DEV",
48 "packageName": "react-dom",
49 - "size": 832345,
50 - "gzip": 188603
49 + "size": 835612,
50 + "gzip": 189016
51 },
52 {
53 "filename": "react-dom.production.min.js",
54 "bundleType": "UMD_PROD",
55 "packageName": "react-dom",
56 - "size": 107683,
57 - "gzip": 34867
56 + "size": 107684,
57 + "gzip": 34777
58 },
59 {
60 "filename": "react-dom.development.js",
61 "bundleType": "NODE_DEV",
62 "packageName": "react-dom",
63 - "size": 826372,
64 - "gzip": 186963
63 + "size": 829867,
64 + "gzip": 187373
65 },
66 {
67 "filename": "react-dom.production.min.js",
68 "bundleType": "NODE_PROD",
69 "packageName": "react-dom",
70 "size": 107664,
71 - "gzip": 34301
71 + "gzip": 34273
72 },
73 {
74 "filename": "ReactDOM-dev.js",
@@ -88,29 +88,29 @@
88 "filename": "react-dom-test-utils.development.js",
89 "bundleType": "UMD_DEV",
90 "packageName": "react-dom",
91 - "size": 48620,
92 - "gzip": 13278
91 + "size": 53238,
92 + "gzip": 14410
93 },
94 {
95 "filename": "react-dom-test-utils.production.min.js",
96 "bundleType": "UMD_PROD",
97 "packageName": "react-dom",
98 - "size": 10184,
99 - "gzip": 3732
98 + "size": 10659,
99 + "gzip": 3913
100 },
101 {
102 "filename": "react-dom-test-utils.development.js",
103 "bundleType": "NODE_DEV",
104 "packageName": "react-dom",
105 - "size": 48334,
106 - "gzip": 13206
105 + "size": 52952,
106 + "gzip": 14343
107 },
108 {
109 "filename": "react-dom-test-utils.production.min.js",
110 "bundleType": "NODE_PROD",
111 "packageName": "react-dom",
112 - "size": 9954,
113 - "gzip": 3660
112 + "size": 10441,
113 + "gzip": 3835
114 },
115 {
116 "filename": "ReactTestUtils-dev.js",
@@ -123,8 +123,8 @@
123 "filename": "react-dom-unstable-native-dependencies.development.js",
124 "bundleType": "UMD_DEV",
125 "packageName": "react-dom",
126 - "size": 62190,
127 - "gzip": 16206
126 + "size": 62213,
127 + "gzip": 16213
128 },
129 {
130 "filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -137,8 +137,8 @@
137 "filename": "react-dom-unstable-native-dependencies.development.js",
138 "bundleType": "NODE_DEV",
139 "packageName": "react-dom",
140 - "size": 61854,
141 - "gzip": 16078
140 + "size": 61877,
141 + "gzip": 16085
142 },
143 {
144 "filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -165,29 +165,29 @@
165 "filename": "react-dom-server.browser.development.js",
166 "bundleType": "UMD_DEV",
167 "packageName": "react-dom",
168 - "size": 136840,
169 - "gzip": 36205
168 + "size": 137930,
169 + "gzip": 36351
170 },
171 {
172 "filename": "react-dom-server.browser.production.min.js",
173 "bundleType": "UMD_PROD",
174 "packageName": "react-dom",
175 - "size": 19363,
176 - "gzip": 7290
175 + "size": 19569,
176 + "gzip": 7381
177 },
178 {
179 "filename": "react-dom-server.browser.development.js",
180 "bundleType": "NODE_DEV",
181 "packageName": "react-dom",
182 - "size": 132878,
183 - "gzip": 35237
182 + "size": 133968,
183 + "gzip": 35384
184 },
185 {
186 "filename": "react-dom-server.browser.production.min.js",
187 "bundleType": "NODE_PROD",
188 "packageName": "react-dom",
189 - "size": 19287,
190 - "gzip": 7290
189 + "size": 19495,
190 + "gzip": 7377
191 },
192 {
193 "filename": "ReactDOMServer-dev.js",
@@ -207,43 +207,43 @@
207 "filename": "react-dom-server.node.development.js",
208 "bundleType": "NODE_DEV",
209 "packageName": "react-dom",
210 - "size": 134867,
211 - "gzip": 35791
210 + "size": 135957,
211 + "gzip": 35941
212 },
213 {
214 "filename": "react-dom-server.node.production.min.js",
215 "bundleType": "NODE_PROD",
216 "packageName": "react-dom",
217 - "size": 20170,
218 - "gzip": 7598
217 + "size": 20377,
218 + "gzip": 7690
219 },
220 {
221 "filename": "react-art.development.js",
222 "bundleType": "UMD_DEV",
223 "packageName": "react-art",
224 - "size": 570224,
225 - "gzip": 123819
224 + "size": 582400,
225 + "gzip": 125635
226 },
227 {
228 "filename": "react-art.production.min.js",
229 "bundleType": "UMD_PROD",
230 "packageName": "react-art",
231 - "size": 99370,
232 - "gzip": 30598
231 + "size": 99129,
232 + "gzip": 30380
233 },
234 {
235 "filename": "react-art.development.js",
236 "bundleType": "NODE_DEV",
237 "packageName": "react-art",
238 - "size": 499479,
239 - "gzip": 106084
238 + "size": 511655,
239 + "gzip": 107868
240 },
241 {
242 "filename": "react-art.production.min.js",
243 "bundleType": "NODE_PROD",
244 "packageName": "react-art",
245 - "size": 63450,
246 - "gzip": 19455
245 + "size": 63295,
246 + "gzip": 19346
247 },
248 {
249 "filename": "ReactART-dev.js",
@@ -291,29 +291,29 @@
291 "filename": "react-test-renderer.development.js",
292 "bundleType": "UMD_DEV",
293 "packageName": "react-test-renderer",
294 - "size": 508633,
295 - "gzip": 107760
294 + "size": 525649,
295 + "gzip": 110641
296 },
297 {
298 "filename": "react-test-renderer.production.min.js",
299 "bundleType": "UMD_PROD",
300 "packageName": "react-test-renderer",
301 - "size": 64479,
302 - "gzip": 19791
301 + "size": 64570,
302 + "gzip": 19714
303 },
304 {
305 "filename": "react-test-renderer.development.js",
306 "bundleType": "NODE_DEV",
307 "packageName": "react-test-renderer",
308 - "size": 504043,
309 - "gzip": 106587
308 + "size": 521059,
309 + "gzip": 109459
310 },
311 {
312 "filename": "react-test-renderer.production.min.js",
313 "bundleType": "NODE_PROD",
314 "packageName": "react-test-renderer",
315 - "size": 64119,
316 - "gzip": 19538
315 + "size": 64265,
316 + "gzip": 19537
317 },
318 {
319 "filename": "ReactTestRenderer-dev.js",
@@ -326,29 +326,29 @@
326 "filename": "react-test-renderer-shallow.development.js",
327 "bundleType": "UMD_DEV",
328 "packageName": "react-test-renderer",
329 - "size": 38113,
330 - "gzip": 9733
329 + "size": 39907,
330 + "gzip": 10032
331 },
332 {
333 "filename": "react-test-renderer-shallow.production.min.js",
334 "bundleType": "UMD_PROD",
335 "packageName": "react-test-renderer",
336 - "size": 11385,
337 - "gzip": 3419
336 + "size": 11688,
337 + "gzip": 3579
338 },
339 {
340 "filename": "react-test-renderer-shallow.development.js",
341 "bundleType": "NODE_DEV",
342 "packageName": "react-test-renderer",
343 - "size": 32275,
344 - "gzip": 8333
343 + "size": 33992,
344 + "gzip": 8604
345 },
346 {
347 "filename": "react-test-renderer-shallow.production.min.js",
348 "bundleType": "NODE_PROD",
349 "packageName": "react-test-renderer",
350 - "size": 12046,
351 - "gzip": 3733
350 + "size": 11884,
351 + "gzip": 3709
352 },
353 {
354 "filename": "ReactShallowRenderer-dev.js",
@@ -361,57 +361,57 @@
361 "filename": "react-noop-renderer.development.js",
362 "bundleType": "NODE_DEV",
363 "packageName": "react-noop-renderer",
364 - "size": 26949,
365 - "gzip": 6362
364 + "size": 35270,
365 + "gzip": 8615
366 },
367 {
368 "filename": "react-noop-renderer.production.min.js",
369 "bundleType": "NODE_PROD",
370 "packageName": "react-noop-renderer",
371 - "size": 9933,
372 - "gzip": 3165
371 + "size": 10470,
372 + "gzip": 3431
373 },
374 {
375 "filename": "react-reconciler.development.js",
376 "bundleType": "NODE_DEV",
377 "packageName": "react-reconciler",
378 - "size": 497879,
379 - "gzip": 104645
378 + "size": 511941,
379 + "gzip": 106665
380 },
381 {
382 "filename": "react-reconciler.production.min.js",
383 "bundleType": "NODE_PROD",
384 "packageName": "react-reconciler",
385 - "size": 64551,
386 - "gzip": 19325
385 + "size": 64562,
386 + "gzip": 19196
387 },
388 {
389 "filename": "react-reconciler-persistent.development.js",
390 "bundleType": "NODE_DEV",
391 "packageName": "react-reconciler",
392 - "size": 495898,
393 - "gzip": 103848
392 + "size": 509778,
393 + "gzip": 105781
394 },
395 {
396 "filename": "react-reconciler-persistent.production.min.js",
397 "bundleType": "NODE_PROD",
398 "packageName": "react-reconciler",
399 - "size": 64562,
400 - "gzip": 19330
399 + "size": 64573,
400 + "gzip": 19202
401 },
402 {
403 "filename": "react-reconciler-reflection.development.js",
404 "bundleType": "NODE_DEV",
405 "packageName": "react-reconciler",
406 "size": 16161,
407 - "gzip": 5096
407 + "gzip": 5015
408 },
409 {
410 "filename": "react-reconciler-reflection.production.min.js",
411 "bundleType": "NODE_PROD",
412 "packageName": "react-reconciler",
413 - "size": 2760,
414 - "gzip": 1244
413 + "size": 2423,
414 + "gzip": 1082
415 },
416 {
417 "filename": "react-call-return.development.js",
@@ -487,57 +487,57 @@
487 "filename": "create-subscription.development.js",
488 "bundleType": "NODE_DEV",
489 "packageName": "create-subscription",
490 - "size": 8538,
491 - "gzip": 2952
490 + "size": 8219,
491 + "gzip": 2827
492 },
493 {
494 "filename": "create-subscription.production.min.js",
495 "bundleType": "NODE_PROD",
496 "packageName": "create-subscription",
497 - "size": 2889,
498 - "gzip": 1344
497 + "size": 2558,
498 + "gzip": 1200
499 },
500 {
501 "filename": "React-dev.js",
502 "bundleType": "FB_WWW_DEV",
503 "packageName": "react",
504 - "size": 61163,
505 - "gzip": 16239
504 + "size": 63801,
505 + "gzip": 16915
506 },
507 {
508 "filename": "React-prod.js",
509 "bundleType": "FB_WWW_PROD",
510 "packageName": "react",
511 - "size": 15734,
512 - "gzip": 4189
511 + "size": 15699,
512 + "gzip": 4193
513 },
514 {
515 "filename": "ReactDOM-dev.js",
516 "bundleType": "FB_WWW_DEV",
517 "packageName": "react-dom",
518 - "size": 851672,
519 - "gzip": 188651
518 + "size": 855350,
519 + "gzip": 189054
520 },
521 {
522 "filename": "ReactDOM-prod.js",
523 "bundleType": "FB_WWW_PROD",
524 "packageName": "react-dom",
525 - "size": 339041,
526 - "gzip": 62418
525 + "size": 339926,
526 + "gzip": 62708
527 },
528 {
529 "filename": "ReactTestUtils-dev.js",
530 "bundleType": "FB_WWW_DEV",
531 "packageName": "react-dom",
532 - "size": 46251,
533 - "gzip": 12476
532 + "size": 51170,
533 + "gzip": 13698
534 },
535 {
536 "filename": "ReactDOMUnstableNativeDependencies-dev.js",
537 "bundleType": "FB_WWW_DEV",
538 "packageName": "react-dom",
539 - "size": 60296,
540 - "gzip": 15251
539 + "size": 60319,
540 + "gzip": 15257
541 },
542 {
543 "filename": "ReactDOMUnstableNativeDependencies-prod.js",
@@ -550,99 +550,99 @@
550 "filename": "ReactDOMServer-dev.js",
551 "bundleType": "FB_WWW_DEV",
552 "packageName": "react-dom",
553 - "size": 135272,
554 - "gzip": 35002
553 + "size": 136328,
554 + "gzip": 35148
555 },
556 {
557 "filename": "ReactDOMServer-prod.js",
558 "bundleType": "FB_WWW_PROD",
559 "packageName": "react-dom",
560 - "size": 46877,
561 - "gzip": 10879
560 + "size": 47596,
561 + "gzip": 10982
562 },
563 {
564 "filename": "ReactART-dev.js",
565 "bundleType": "FB_WWW_DEV",
566 "packageName": "react-art",
567 - "size": 509037,
568 - "gzip": 105217
567 + "size": 521820,
568 + "gzip": 107152
569 },
570 {
571 "filename": "ReactART-prod.js",
572 "bundleType": "FB_WWW_PROD",
573 "packageName": "react-art",
574 - "size": 201874,
575 - "gzip": 34245
574 + "size": 202109,
575 + "gzip": 34176
576 },
577 {
578 "filename": "ReactNativeRenderer-dev.js",
579 "bundleType": "RN_FB_DEV",
580 "packageName": "react-native-renderer",
581 - "size": 637829,
582 - "gzip": 136280
581 + "size": 645983,
582 + "gzip": 137694
583 },
584 {
585 "filename": "ReactNativeRenderer-prod.js",
586 "bundleType": "RN_FB_PROD",
587 "packageName": "react-native-renderer",
588 - "size": 251569,
589 - "gzip": 43973
588 + "size": 252030,
589 + "gzip": 44064
590 },
591 {
592 "filename": "ReactNativeRenderer-dev.js",
593 "bundleType": "RN_OSS_DEV",
594 "packageName": "react-native-renderer",
595 - "size": 637742,
596 - "gzip": 136246
595 + "size": 645895,
596 + "gzip": 137660
597 },
598 {
599 "filename": "ReactNativeRenderer-prod.js",
600 "bundleType": "RN_OSS_PROD",
601 "packageName": "react-native-renderer",
602 - "size": 251583,
603 - "gzip": 43970
602 + "size": 252044,
603 + "gzip": 44061
604 },
605 {
606 "filename": "ReactFabric-dev.js",
607 "bundleType": "RN_FB_DEV",
608 "packageName": "react-native-renderer",
609 - "size": 628028,
610 - "gzip": 133874
609 + "size": 634566,
610 + "gzip": 134983
611 },
612 {
613 "filename": "ReactFabric-prod.js",
614 "bundleType": "RN_FB_PROD",
615 "packageName": "react-native-renderer",
616 - "size": 245497,
617 - "gzip": 42746
616 + "size": 245276,
617 + "gzip": 42773
618 },
619 {
620 "filename": "ReactFabric-dev.js",
621 "bundleType": "RN_OSS_DEV",
622 "packageName": "react-native-renderer",
623 - "size": 627933,
624 - "gzip": 133834
623 + "size": 634470,
624 + "gzip": 134930
625 },
626 {
627 "filename": "ReactFabric-prod.js",
628 "bundleType": "RN_OSS_PROD",
629 "packageName": "react-native-renderer",
630 - "size": 245503,
631 - "gzip": 42741
630 + "size": 245282,
631 + "gzip": 42767
632 },
633 {
634 "filename": "ReactTestRenderer-dev.js",
635 "bundleType": "FB_WWW_DEV",
636 "packageName": "react-test-renderer",
637 - "size": 514514,
638 - "gzip": 106185
637 + "size": 532823,
638 + "gzip": 109328
639 },
640 {
641 "filename": "ReactShallowRenderer-dev.js",
642 "bundleType": "FB_WWW_DEV",
643 "packageName": "react-test-renderer",
644 - "size": 30652,
645 - "gzip": 7761
644 + "size": 33767,
645 + "gzip": 8435
646 },
647 {
648 "filename": "ReactIs-dev.js",
@@ -704,36 +704,36 @@
704 "filename": "react-noop-renderer-persistent.development.js",
705 "bundleType": "NODE_DEV",
706 "packageName": "react-noop-renderer",
707 - "size": 27068,
708 - "gzip": 6375
707 + "size": 35389,
708 + "gzip": 8630
709 },
710 {
711 "filename": "react-noop-renderer-persistent.production.min.js",
712 "bundleType": "NODE_PROD",
713 "packageName": "react-noop-renderer",
714 - "size": 9955,
715 - "gzip": 3170
714 + "size": 10492,
715 + "gzip": 3436
716 },
717 {
718 "filename": "react-dom.profiling.min.js",
719 "bundleType": "NODE_PROFILING",
720 "packageName": "react-dom",
721 - "size": 110839,
722 - "gzip": 34944
721 + "size": 110841,
722 + "gzip": 34901
723 },
724 {
725 "filename": "ReactNativeRenderer-profiling.js",
726 "bundleType": "RN_OSS_PROFILING",
727 "packageName": "react-native-renderer",
728 - "size": 257964,
729 - "gzip": 45359
728 + "size": 258447,
729 + "gzip": 45443
730 },
731 {
732 "filename": "ReactFabric-profiling.js",
733 "bundleType": "RN_OSS_PROFILING",
734 "packageName": "react-native-renderer",
735 - "size": 250954,
736 - "gzip": 44143
735 + "size": 250755,
736 + "gzip": 44122
737 },
738 {
739 "filename": "Scheduler-dev.js",
@@ -760,50 +760,50 @@
760 "filename": "React-profiling.js",
761 "bundleType": "FB_WWW_PROFILING",
762 "packageName": "react",
763 - "size": 15734,
764 - "gzip": 4189
763 + "size": 15699,
764 + "gzip": 4193
765 },
766 {
767 "filename": "ReactDOM-profiling.js",
768 "bundleType": "FB_WWW_PROFILING",
769 "packageName": "react-dom",
770 - "size": 345581,
771 - "gzip": 63810
770 + "size": 346531,
771 + "gzip": 64085
772 },
773 {
774 "filename": "ReactNativeRenderer-profiling.js",
775 "bundleType": "RN_FB_PROFILING",
776 "packageName": "react-native-renderer",
777 - "size": 257945,
778 - "gzip": 45363
777 + "size": 258428,
778 + "gzip": 45445
779 },
780 {
781 "filename": "ReactFabric-profiling.js",
782 "bundleType": "RN_FB_PROFILING",
783 "packageName": "react-native-renderer",
784 - "size": 250943,
785 - "gzip": 44145
784 + "size": 250744,
785 + "gzip": 44126
786 },
787 {
788 "filename": "react.profiling.min.js",
789 "bundleType": "UMD_PROFILING",
790 "packageName": "react",
791 - "size": 14818,
792 - "gzip": 5369
791 + "size": 14552,
792 + "gzip": 5255
793 },
794 {
795 "filename": "react-dom.profiling.min.js",
796 "bundleType": "UMD_PROFILING",
797 "packageName": "react-dom",
798 - "size": 110730,
799 - "gzip": 35485
798 + "size": 110732,
799 + "gzip": 35443
800 },
801 {
802 "filename": "scheduler-tracing.development.js",
803 "bundleType": "NODE_DEV",
804 "packageName": "scheduler",
805 - "size": 10878,
806 - "gzip": 2594
805 + "size": 11062,
806 + "gzip": 2681
807 },
808 {
809 "filename": "scheduler-tracing.production.min.js",
@@ -823,8 +823,8 @@
823 "filename": "SchedulerTracing-dev.js",
824 "bundleType": "FB_WWW_DEV",
825 "packageName": "scheduler",
826 - "size": 10267,
827 - "gzip": 2151
826 + "size": 10470,
827 + "gzip": 2260
828 },
829 {
830 "filename": "SchedulerTracing-prod.js",
@@ -886,29 +886,29 @@
886 "filename": "jest-react.development.js",
887 "bundleType": "NODE_DEV",
888 "packageName": "jest-react",
889 - "size": 7455,
890 - "gzip": 2737
889 + "size": 7100,
890 + "gzip": 2546
891 },
892 {
893 "filename": "jest-react.production.min.js",
894 "bundleType": "NODE_PROD",
895 "packageName": "jest-react",
896 - "size": 2930,
897 - "gzip": 1442
896 + "size": 2599,
897 + "gzip": 1299
898 },
899 {
900 "filename": "JestReact-dev.js",
901 "bundleType": "FB_WWW_DEV",
902 "packageName": "jest-react",
903 - "size": 4208,
904 - "gzip": 1490
903 + "size": 5010,
904 + "gzip": 1757
905 },
906 {
907 "filename": "JestReact-prod.js",
908 "bundleType": "FB_WWW_PROD",
909 "packageName": "jest-react",
910 - "size": 3592,
911 - "gzip": 1315
910 + "size": 3492,
911 + "gzip": 1287
912 },
913 {
914 "filename": "react-debug-tools.development.js",
@@ -928,15 +928,15 @@
928 "filename": "eslint-plugin-react-hooks.development.js",
929 "bundleType": "NODE_DEV",
930 "packageName": "eslint-plugin-react-hooks",
931 - "size": 75099,
932 - "gzip": 17223
931 + "size": 77541,
932 + "gzip": 17683
933 },
934 {
935 "filename": "eslint-plugin-react-hooks.production.min.js",
936 "bundleType": "NODE_PROD",
937 "packageName": "eslint-plugin-react-hooks",
938 - "size": 19731,
939 - "gzip": 6811
938 + "size": 20485,
939 + "gzip": 7082
940 },
941 {
942 "filename": "ReactDOMFizzServer-dev.js",
@@ -1026,71 +1026,71 @@
1026 "filename": "ESLintPluginReactHooks-dev.js",
1027 "bundleType": "FB_WWW_DEV",
1028 "packageName": "eslint-plugin-react-hooks",
1029 - "size": 80483,
1030 - "gzip": 17729
1029 + "size": 83133,
1030 + "gzip": 18239
1031 },
1032 {
1033 "filename": "react-dom-unstable-fire.development.js",
1034 "bundleType": "UMD_DEV",
1035 "packageName": "react-dom",
1036 - "size": 832471,
1037 - "gzip": 188731
1036 + "size": 835944,
1037 + "gzip": 189154
1038 },
1039 {
1040 "filename": "react-dom-unstable-fire.production.min.js",
1041 "bundleType": "UMD_PROD",
1042 "packageName": "react-dom",
1043 - "size": 107698,
1044 - "gzip": 34877
1043 + "size": 107699,
1044 + "gzip": 34786
1045 },
1046 {
1047 "filename": "react-dom-unstable-fire.profiling.min.js",
1048 "bundleType": "UMD_PROFILING",
1049 "packageName": "react-dom",
1050 - "size": 110745,
1051 - "gzip": 35493
1050 + "size": 110747,
1051 + "gzip": 35451
1052 },
1053 {
1054 "filename": "react-dom-unstable-fire.development.js",
1055 "bundleType": "NODE_DEV",
1056 "packageName": "react-dom",
1057 - "size": 826725,
1058 - "gzip": 187104
1057 + "size": 830198,
1058 + "gzip": 187505
1059 },
1060 {
1061 "filename": "react-dom-unstable-fire.production.min.js",
1062 "bundleType": "NODE_PROD",
1063 "packageName": "react-dom",
1064 "size": 107678,
1065 - "gzip": 34310
1065 + "gzip": 34282
1066 },
1067 {
1068 "filename": "react-dom-unstable-fire.profiling.min.js",
1069 "bundleType": "NODE_PROFILING",
1070 "packageName": "react-dom",
1071 - "size": 110853,
1072 - "gzip": 34953
1071 + "size": 110855,
1072 + "gzip": 34911
1073 },
1074 {
1075 "filename": "ReactFire-dev.js",
1076 "bundleType": "FB_WWW_DEV",
1077 "packageName": "react-dom",
1078 - "size": 850863,
1079 - "gzip": 188551
1078 + "size": 854519,
1079 + "gzip": 189023
1080 },
1081 {
1082 "filename": "ReactFire-prod.js",
1083 "bundleType": "FB_WWW_PROD",
1084 "packageName": "react-dom",
1085 - "size": 327881,
1086 - "gzip": 60185
1085 + "size": 328314,
1086 + "gzip": 60295
1087 },
1088 {
1089 "filename": "ReactFire-profiling.js",
1090 "bundleType": "FB_WWW_PROFILING",
1091 "packageName": "react-dom",
1092 - "size": 334366,
1093 - "gzip": 61586
1092 + "size": 334864,
1093 + "gzip": 61709
1094 },
1095 {
1096 "filename": "jest-mock-scheduler.development.js",
@@ -1152,29 +1152,253 @@
1152 "filename": "react-events.development.js",
1153 "bundleType": "NODE_DEV",
1154 "packageName": "react-events",
1155 - "size": 1135,
1156 - "gzip": 623
1155 + "size": 990,
1156 + "gzip": 545
1157 },
1158 {
1159 "filename": "react-events.production.min.js",
1160 "bundleType": "NODE_PROD",
1161 "packageName": "react-events",
1162 - "size": 448,
1163 - "gzip": 328
1162 + "size": 506,
1163 + "gzip": 343
1164 },
1165 {
1166 "filename": "ReactEvents-dev.js",
1167 "bundleType": "FB_WWW_DEV",
1168 "packageName": "react-events",
1169 - "size": 1106,
1170 - "gzip": 613
1169 + "size": 956,
1170 + "gzip": 536
1171 },
1172 {
1173 "filename": "ReactEvents-prod.js",
1174 "bundleType": "FB_WWW_PROD",
1175 "packageName": "react-events",
1176 - "size": 643,
1177 - "gzip": 377
1176 + "size": 687,
1177 + "gzip": 410
1178 + },
1179 + {
1180 + "filename": "react-events.development.js",
1181 + "bundleType": "UMD_DEV",
1182 + "packageName": "react-events",
1183 + "size": 1183,
1184 + "gzip": 605
1185 + },
1186 + {
1187 + "filename": "react-events.production.min.js",
1188 + "bundleType": "UMD_PROD",
1189 + "packageName": "react-events",
1190 + "size": 676,
1191 + "gzip": 420
1192 + },
1193 + {
1194 + "filename": "react-events-press.development.js",
1195 + "bundleType": "UMD_DEV",
1196 + "packageName": "react-events",
1197 + "size": 10325,
1198 + "gzip": 2630
1199 + },
1200 + {
1201 + "filename": "react-events-press.production.min.js",
1202 + "bundleType": "UMD_PROD",
1203 + "packageName": "react-events",
1204 + "size": 4058,
1205 + "gzip": 1507
1206 + },
1207 + {
1208 + "filename": "react-events-press.development.js",
1209 + "bundleType": "NODE_DEV",
1210 + "packageName": "react-events",
1211 + "size": 10151,
1212 + "gzip": 2584
1213 + },
1214 + {
1215 + "filename": "react-events-press.production.min.js",
1216 + "bundleType": "NODE_PROD",
1217 + "packageName": "react-events",
1218 + "size": 3892,
1219 + "gzip": 1451
1220 + },
1221 + {
1222 + "filename": "ReactEventsPress-dev.js",
1223 + "bundleType": "FB_WWW_DEV",
1224 + "packageName": "react-events",
1225 + "size": 10480,
1226 + "gzip": 2636
1227 + },
1228 + {
1229 + "filename": "ReactEventsPress-prod.js",
1230 + "bundleType": "FB_WWW_PROD",
1231 + "packageName": "react-events",
1232 + "size": 8000,
1233 + "gzip": 1905
1234 + },
1235 + {
1236 + "filename": "react-events-hover.development.js",
1237 + "bundleType": "UMD_DEV",
1238 + "packageName": "react-events",
1239 + "size": 5271,
1240 + "gzip": 1416
1241 + },
1242 + {
1243 + "filename": "react-events-hover.production.min.js",
1244 + "bundleType": "UMD_PROD",
1245 + "packageName": "react-events",
1246 + "size": 2312,
1247 + "gzip": 923
1248 + },
1249 + {
1250 + "filename": "react-events-hover.development.js",
1251 + "bundleType": "NODE_DEV",
1252 + "packageName": "react-events",
1253 + "size": 5097,
1254 + "gzip": 1372
1255 + },
1256 + {
1257 + "filename": "react-events-hover.production.min.js",
1258 + "bundleType": "NODE_PROD",
1259 + "packageName": "react-events",
1260 + "size": 2147,
1261 + "gzip": 865
1262 + },
1263 + {
1264 + "filename": "ReactEventsHover-dev.js",
1265 + "bundleType": "FB_WWW_DEV",
1266 + "packageName": "react-events",
1267 + "size": 5113,
1268 + "gzip": 1386
1269 + },
1270 + {
1271 + "filename": "ReactEventsHover-prod.js",
1272 + "bundleType": "FB_WWW_PROD",
1273 + "packageName": "react-events",
1274 + "size": 4279,
1275 + "gzip": 1130
1276 + },
1277 + {
1278 + "filename": "react-events-focus.development.js",
1279 + "bundleType": "UMD_DEV",
1280 + "packageName": "react-events",
1281 + "size": 3446,
1282 + "gzip": 1112
1283 + },
1284 + {
1285 + "filename": "react-events-focus.production.min.js",
1286 + "bundleType": "UMD_PROD",
1287 + "packageName": "react-events",
1288 + "size": 1563,
1289 + "gzip": 721
1290 + },
1291 + {
1292 + "filename": "react-events-focus.development.js",
1293 + "bundleType": "NODE_DEV",
1294 + "packageName": "react-events",
1295 + "size": 3272,
1296 + "gzip": 1068
1297 + },
1298 + {
1299 + "filename": "react-events-focus.production.min.js",
1300 + "bundleType": "NODE_PROD",
1301 + "packageName": "react-events",
1302 + "size": 1392,
1303 + "gzip": 659
1304 + },
1305 + {
1306 + "filename": "ReactEventsFocus-dev.js",
1307 + "bundleType": "FB_WWW_DEV",
1308 + "packageName": "react-events",
1309 + "size": 3242,
1310 + "gzip": 1058
1311 + },
1312 + {
1313 + "filename": "ReactEventsFocus-prod.js",
1314 + "bundleType": "FB_WWW_PROD",
1315 + "packageName": "react-events",
1316 + "size": 2552,
1317 + "gzip": 827
1318 + },
1319 + {
1320 + "filename": "react-events-swipe.development.js",
1321 + "bundleType": "UMD_DEV",
1322 + "packageName": "react-events",
1323 + "size": 8479,
1324 + "gzip": 2604
1325 + },
1326 + {
1327 + "filename": "react-events-swipe.production.min.js",
1328 + "bundleType": "UMD_PROD",
1329 + "packageName": "react-events",
1330 + "size": 3531,
1331 + "gzip": 1625
1332 + },
1333 + {
1334 + "filename": "react-events-swipe.development.js",
1335 + "bundleType": "NODE_DEV",
1336 + "packageName": "react-events",
1337 + "size": 8305,
1338 + "gzip": 2571
1339 + },
1340 + {
1341 + "filename": "react-events-swipe.production.min.js",
1342 + "bundleType": "NODE_PROD",
1343 + "packageName": "react-events",
1344 + "size": 3364,
1345 + "gzip": 1569
1346 + },
1347 + {
1348 + "filename": "ReactEventsSwipe-dev.js",
1349 + "bundleType": "FB_WWW_DEV",
1350 + "packageName": "react-events",
1351 + "size": 6360,
1352 + "gzip": 1814
1353 + },
1354 + {
1355 + "filename": "ReactEventsSwipe-prod.js",
1356 + "bundleType": "FB_WWW_PROD",
1357 + "packageName": "react-events",
1358 + "size": 6089,
1359 + "gzip": 1563
1360 + },
1361 + {
1362 + "filename": "react-events-drag.development.js",
1363 + "bundleType": "UMD_DEV",
1364 + "packageName": "react-events",
1365 + "size": 7733,
1366 + "gzip": 2450
1367 + },
1368 + {
1369 + "filename": "react-events-drag.production.min.js",
1370 + "bundleType": "UMD_PROD",
1371 + "packageName": "react-events",
1372 + "size": 3278,
1373 + "gzip": 1489
1374 + },
1375 + {
1376 + "filename": "react-events-drag.development.js",
1377 + "bundleType": "NODE_DEV",
1378 + "packageName": "react-events",
1379 + "size": 7560,
1380 + "gzip": 2415
1381 + },
1382 + {
1383 + "filename": "react-events-drag.production.min.js",
1384 + "bundleType": "NODE_PROD",
1385 + "packageName": "react-events",
1386 + "size": 3112,
1387 + "gzip": 1429
1388 + },
1389 + {
1390 + "filename": "ReactEventsDrag-dev.js",
1391 + "bundleType": "FB_WWW_DEV",
1392 + "packageName": "react-events",
1393 + "size": 5706,
1394 + "gzip": 1684
1395 + },
1396 + {
1397 + "filename": "ReactEventsDrag-prod.js",
1398 + "bundleType": "FB_WWW_PROD",
1399 + "packageName": "react-events",
1400 + "size": 5245,
1401 + "gzip": 1368
1402 }
1403 ]
1404 }
\ No newline at end of file