diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js index 3e1c67087c12a..d075b7097de2a 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.internal.js @@ -22,6 +22,11 @@ describe('ReactHooksInspection', () => { ReactDebugTools = require('react-debug-tools'); }); + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + it('should inspect a simple useResponder hook', () => { const TestResponder = React.DEPRECATED_createResponder('TestResponder', {}); diff --git a/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js b/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js index 1639befc76bb9..3829a4377ee34 100644 --- a/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js +++ b/packages/react-devtools-shared/src/devtools/ContextMenu/useContextMenu.js @@ -19,18 +19,22 @@ export default function useContextMenu({ }: {| data: Object, id: string, - ref: ElementRef, + ref: {current: ElementRef<'div'> | null}, |}) { const {showMenu} = useContext(RegistryContext); useEffect(() => { if (ref.current !== null) { - const handleContextMenu = event => { + const handleContextMenu = (event: MouseEvent | TouchEvent) => { event.preventDefault(); event.stopPropagation(); - const pageX = event.pageX || (event.touches && event.touches[0].pageX); - const pageY = event.pageY || (event.touches && event.touches[0].pageY); + const pageX = + event.pageX || + (event.touches && ((event: any): TouchEvent).touches[0].pageX); + const pageY = + event.pageY || + (event.touches && ((event: any): TouchEvent).touches[0].pageY); showMenu({data, id, pageX, pageY}); }; diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js new file mode 100644 index 0000000000000..96ce6a6cc35c8 --- /dev/null +++ b/packages/react-dom/index.classic.fb.js @@ -0,0 +1,46 @@ +/** + * 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 {addUserTimingListener} from 'shared/ReactFeatureFlags'; +import {isEnabled} from './src/events/ReactDOMEventListener'; +import {getClosestInstanceFromNode} from './src/client/ReactDOMComponentTree'; + +import {__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/client/ReactDOM'; + +// For classic WWW builds, include a few internals that are already in use. +Object.assign((__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: any), { + ReactBrowserEventEmitter: { + isEnabled, + }, + ReactDOMComponentTree: { + getClosestInstanceFromNode, + }, + // Perf experiment + addUserTimingListener, +}); + +export { + createPortal, + unstable_batchedUpdates, + flushSync, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + version, + findDOMNode, + hydrate, + render, + unmountComponentAtNode, + createRoot, + createBlockingRoot, + unstable_discreteUpdates, + unstable_flushDiscreteUpdates, + unstable_flushControlled, + unstable_scheduleHydration, + unstable_renderSubtreeIntoContainer, + unstable_createPortal, +} from './src/client/ReactDOM'; diff --git a/packages/react-dom/index.experimental.js b/packages/react-dom/index.experimental.js new file mode 100644 index 0000000000000..b771d0105b8e5 --- /dev/null +++ b/packages/react-dom/index.experimental.js @@ -0,0 +1,34 @@ +/** + * 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 + */ + +export { + createPortal, + unstable_batchedUpdates, + flushSync, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + version, + // Disabled behind disableLegacyReactDOMAPIs + findDOMNode, + hydrate, + render, + unmountComponentAtNode, + // exposeConcurrentModeAPIs + createRoot, + createBlockingRoot, + unstable_discreteUpdates, + unstable_flushDiscreteUpdates, + unstable_flushControlled, + unstable_scheduleHydration, + // Disabled behind disableUnstableRenderSubtreeIntoContainer + unstable_renderSubtreeIntoContainer, + // Disabled behind disableUnstableCreatePortal + // Temporary alias since we already shipped React 16 RC with it. + // TODO: remove in React 17. + unstable_createPortal, +} from './src/client/ReactDOM'; diff --git a/packages/react-dom/index.fb.js b/packages/react-dom/index.fb.js deleted file mode 100644 index ea901748d6949..0000000000000 --- a/packages/react-dom/index.fb.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * 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. - */ - -'use strict'; - -const ReactDOMFB = require('./src/client/ReactDOMFB'); - -// TODO: decide on the top-level export form. -// This is hacky but makes it work with both Rollup and Jest. -module.exports = ReactDOMFB.default || ReactDOMFB; diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js index 2a016ba16e9db..3714de11244ff 100644 --- a/packages/react-dom/index.js +++ b/packages/react-dom/index.js @@ -7,10 +7,4 @@ * @flow */ -'use strict'; - -const ReactDOM = require('./src/client/ReactDOM'); - -// TODO: decide on the top-level export form. -// This is hacky but makes it work with both Rollup and Jest. -module.exports = ReactDOM.default || ReactDOM; +export * from './src/client/ReactDOM'; diff --git a/packages/react-dom/index.modern.fb.js b/packages/react-dom/index.modern.fb.js new file mode 100644 index 0000000000000..bcb213ad4ee35 --- /dev/null +++ b/packages/react-dom/index.modern.fb.js @@ -0,0 +1,22 @@ +/** + * 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 + */ + +export { + createPortal, + unstable_batchedUpdates, + flushSync, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + version, + createRoot, + createBlockingRoot, + unstable_discreteUpdates, + unstable_flushDiscreteUpdates, + unstable_flushControlled, + unstable_scheduleHydration, +} from './src/client/ReactDOM'; diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js new file mode 100644 index 0000000000000..00d52806bab59 --- /dev/null +++ b/packages/react-dom/index.stable.js @@ -0,0 +1,24 @@ +/** + * 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 + */ + +export { + createPortal, + unstable_batchedUpdates, + flushSync, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + version, + findDOMNode, + hydrate, + render, + unmountComponentAtNode, + unstable_renderSubtreeIntoContainer, + // Temporary alias since we already shipped React 16 RC with it. + // TODO: remove in React 17. + unstable_createPortal, +} from './src/client/ReactDOM'; diff --git a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js index efc605a9edb3a..ef872dfeb47e0 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiber-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiber-test.js @@ -13,8 +13,6 @@ const React = require('react'); const ReactDOM = require('react-dom'); const PropTypes = require('prop-types'); -const ReactFeatureFlags = require('shared/ReactFeatureFlags'); - describe('ReactDOMFiber', () => { let container; @@ -249,7 +247,7 @@ describe('ReactDOMFiber', () => { }); // TODO: remove in React 17 - if (!ReactFeatureFlags.disableUnstableCreatePortal) { + if (!__EXPERIMENTAL__) { it('should support unstable_createPortal alias', () => { const portalContainer = document.createElement('div'); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 7a25979987b4f..f6f7c4979bc91 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -86,8 +86,6 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler = require('scheduler'); Suspense = React.Suspense; SuspenseList = React.SuspenseList; - - useHover = require('react-interactions/events/hover').useHover; }); if (!__EXPERIMENTAL__) { @@ -2368,120 +2366,124 @@ describe('ReactDOMServerPartialHydration', () => { document.body.removeChild(container); }); - it('blocks only on the last continuous event (Responder system)', async () => { - let suspend1 = false; - let resolve1; - let promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise)); - let suspend2 = false; - let resolve2; - let promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise)); - - function First({text}) { - if (suspend1) { - throw promise1; - } else { - return 'Hello'; + if (__EXPERIMENTAL__) { + it('blocks only on the last continuous event (Responder system)', async () => { + useHover = require('react-interactions/events/hover').useHover; + + let suspend1 = false; + let resolve1; + let promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise)); + let suspend2 = false; + let resolve2; + let promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise)); + + function First({text}) { + if (suspend1) { + throw promise1; + } else { + return 'Hello'; + } } - } - function Second({text}) { - if (suspend2) { - throw promise2; - } else { - return 'World'; + function Second({text}) { + if (suspend2) { + throw promise2; + } else { + return 'World'; + } } - } - - let ops = []; - function App() { - const listener1 = useHover({ - onHoverStart() { - ops.push('Hover Start First'); - }, - onHoverEnd() { - ops.push('Hover End First'); - }, - }); - const listener2 = useHover({ - onHoverStart() { - ops.push('Hover Start Second'); - }, - onHoverEnd() { - ops.push('Hover End Second'); - }, - }); - return ( -
- - - {/* We suspend after to test what happens when we eager + let ops = []; + + function App() { + const listener1 = useHover({ + onHoverStart() { + ops.push('Hover Start First'); + }, + onHoverEnd() { + ops.push('Hover End First'); + }, + }); + const listener2 = useHover({ + onHoverStart() { + ops.push('Hover Start Second'); + }, + onHoverEnd() { + ops.push('Hover End Second'); + }, + }); + return ( +
+ + + {/* We suspend after to test what happens when we eager attach the listener. */} - - - - - - - -
- ); - } + +
+ + + + + +
+ ); + } - let finalHTML = ReactDOMServer.renderToString(); - let container = document.createElement('div'); - container.innerHTML = finalHTML; + let finalHTML = ReactDOMServer.renderToString(); + let container = document.createElement('div'); + container.innerHTML = finalHTML; - // We need this to be in the document since we'll dispatch events on it. - document.body.appendChild(container); + // We need this to be in the document since we'll dispatch events on it. + document.body.appendChild(container); - let appDiv = container.getElementsByTagName('div')[0]; - let firstSpan = appDiv.getElementsByTagName('span')[0]; - let secondSpan = appDiv.getElementsByTagName('span')[1]; - expect(firstSpan.textContent).toBe(''); - expect(secondSpan.textContent).toBe('World'); + let appDiv = container.getElementsByTagName('div')[0]; + let firstSpan = appDiv.getElementsByTagName('span')[0]; + let secondSpan = appDiv.getElementsByTagName('span')[1]; + expect(firstSpan.textContent).toBe(''); + expect(secondSpan.textContent).toBe('World'); - // On the client we don't have all data yet but we want to start - // hydrating anyway. - suspend1 = true; - suspend2 = true; - let root = ReactDOM.createRoot(container, {hydrate: true}); - root.render(); + // On the client we don't have all data yet but we want to start + // hydrating anyway. + suspend1 = true; + suspend2 = true; + let root = ReactDOM.createRoot(container, {hydrate: true}); + root.render(); - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + Scheduler.unstable_flushAll(); + jest.runAllTimers(); - dispatchMouseEvent(appDiv, null); - dispatchMouseEvent(firstSpan, appDiv); - dispatchMouseEvent(secondSpan, firstSpan); + dispatchMouseEvent(appDiv, null); + dispatchMouseEvent(firstSpan, appDiv); + dispatchMouseEvent(secondSpan, firstSpan); - // Neither target is yet hydrated. - expect(ops).toEqual([]); + // Neither target is yet hydrated. + expect(ops).toEqual([]); - // Resolving the second promise so that rendering can complete. - suspend2 = false; - resolve2(); - await promise2; + // Resolving the second promise so that rendering can complete. + suspend2 = false; + resolve2(); + await promise2; - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + Scheduler.unstable_flushAll(); + jest.runAllTimers(); - // We've unblocked the current hover target so we should be - // able to replay it now. - expect(ops).toEqual(['Hover Start Second']); + // We've unblocked the current hover target so we should be + // able to replay it now. + expect(ops).toEqual(['Hover Start Second']); - // Resolving the first promise has no effect now. - suspend1 = false; - resolve1(); - await promise1; + // Resolving the first promise has no effect now. + suspend1 = false; + resolve1(); + await promise1; - Scheduler.unstable_flushAll(); - jest.runAllTimers(); + Scheduler.unstable_flushAll(); + jest.runAllTimers(); - expect(ops).toEqual(['Hover Start Second']); + expect(ops).toEqual(['Hover Start Second']); - document.body.removeChild(container); - }); + document.body.removeChild(container); + }); + } it('finishes normal pri work before continuing to hydrate a retry', async () => { let suspend = false; diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index 02d1dff1a9ec6..3fb9169c1dc6e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -17,7 +17,6 @@ let ReactDOMServer; let ReactTestUtils; let Scheduler; let Suspense; -let usePress; function dispatchMouseHoverEvent(to, from) { if (!to) { @@ -106,7 +105,6 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactTestUtils = require('react-dom/test-utils'); Scheduler = require('scheduler'); Suspense = React.Suspense; - usePress = require('react-interactions/events/press').usePress; }); if (!__EXPERIMENTAL__) { @@ -352,240 +350,246 @@ describe('ReactDOMServerSelectiveHydration', () => { document.body.removeChild(container); }); - it('hydrates the target boundary synchronously during a click (flare)', async () => { - function Child({text}) { - Scheduler.unstable_yieldValue(text); - const listener = usePress({ - onPress() { - Scheduler.unstable_yieldValue('Clicked ' + text); - }, - }); + if (__EXPERIMENTAL__) { + it('hydrates the target boundary synchronously during a click (flare)', async () => { + let usePress = require('react-interactions/events/press').usePress; - return {text}; - } + function Child({text}) { + Scheduler.unstable_yieldValue(text); + const listener = usePress({ + onPress() { + Scheduler.unstable_yieldValue('Clicked ' + text); + }, + }); - function App() { - Scheduler.unstable_yieldValue('App'); - return ( -
- - - - - - -
- ); - } + return {text}; + } - let finalHTML = ReactDOMServer.renderToString(); + function App() { + Scheduler.unstable_yieldValue('App'); + return ( +
+ + + + + + +
+ ); + } - expect(Scheduler).toHaveYielded(['App', 'A', 'B']); + let finalHTML = ReactDOMServer.renderToString(); - let container = document.createElement('div'); - // We need this to be in the document since we'll dispatch events on it. - document.body.appendChild(container); + expect(Scheduler).toHaveYielded(['App', 'A', 'B']); - container.innerHTML = finalHTML; + let container = document.createElement('div'); + // We need this to be in the document since we'll dispatch events on it. + document.body.appendChild(container); - let root = ReactDOM.createRoot(container, {hydrate: true}); - root.render(); + container.innerHTML = finalHTML; - // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + let root = ReactDOM.createRoot(container, {hydrate: true}); + root.render(); - let span = container.getElementsByTagName('span')[1]; + // Nothing has been hydrated so far. + expect(Scheduler).toHaveYielded([]); - let target = createEventTarget(span); + let span = container.getElementsByTagName('span')[1]; - // This should synchronously hydrate the root App and the second suspense - // boundary. - let preventDefault = jest.fn(); - target.virtualclick({preventDefault}); + let target = createEventTarget(span); - // The event should have been canceled because we called preventDefault. - expect(preventDefault).toHaveBeenCalled(); + // This should synchronously hydrate the root App and the second suspense + // boundary. + let preventDefault = jest.fn(); + target.virtualclick({preventDefault}); - // We rendered App, B and then invoked the event without rendering A. - expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']); + // The event should have been canceled because we called preventDefault. + expect(preventDefault).toHaveBeenCalled(); - // After continuing the scheduler, we finally hydrate A. - expect(Scheduler).toFlushAndYield(['A']); + // We rendered App, B and then invoked the event without rendering A. + expect(Scheduler).toHaveYielded(['App', 'B', 'Clicked B']); - document.body.removeChild(container); - }); + // After continuing the scheduler, we finally hydrate A. + expect(Scheduler).toFlushAndYield(['A']); - it('hydrates at higher pri if sync did not work first time (flare)', async () => { - let suspend = false; - let resolve; - let promise = new Promise(resolvePromise => (resolve = resolvePromise)); + document.body.removeChild(container); + }); - function Child({text}) { - if ((text === 'A' || text === 'D') && suspend) { - throw promise; + it('hydrates at higher pri if sync did not work first time (flare)', async () => { + let usePress = require('react-interactions/events/press').usePress; + let suspend = false; + let resolve; + let promise = new Promise(resolvePromise => (resolve = resolvePromise)); + + function Child({text}) { + if ((text === 'A' || text === 'D') && suspend) { + throw promise; + } + Scheduler.unstable_yieldValue(text); + + const listener = usePress({ + onPress() { + Scheduler.unstable_yieldValue('Clicked ' + text); + }, + }); + return {text}; } - Scheduler.unstable_yieldValue(text); - const listener = usePress({ - onPress() { - Scheduler.unstable_yieldValue('Clicked ' + text); - }, - }); - return {text}; - } + function App() { + Scheduler.unstable_yieldValue('App'); + return ( +
+ + + + + + + + + + + + +
+ ); + } - function App() { - Scheduler.unstable_yieldValue('App'); - return ( -
- - - - - - - - - - - - -
- ); - } + let finalHTML = ReactDOMServer.renderToString(); - let finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + let container = document.createElement('div'); + // We need this to be in the document since we'll dispatch events on it. + document.body.appendChild(container); - let container = document.createElement('div'); - // We need this to be in the document since we'll dispatch events on it. - document.body.appendChild(container); + container.innerHTML = finalHTML; - container.innerHTML = finalHTML; + let spanD = container.getElementsByTagName('span')[3]; - let spanD = container.getElementsByTagName('span')[3]; + suspend = true; - suspend = true; + // A and D will be suspended. We'll click on D which should take + // priority, after we unsuspend. + let root = ReactDOM.createRoot(container, {hydrate: true}); + root.render(); - // A and D will be suspended. We'll click on D which should take - // priority, after we unsuspend. - let root = ReactDOM.createRoot(container, {hydrate: true}); - root.render(); + // Nothing has been hydrated so far. + expect(Scheduler).toHaveYielded([]); - // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + // This click target cannot be hydrated yet because it's suspended. + let result = dispatchClickEvent(spanD); - // This click target cannot be hydrated yet because it's suspended. - let result = dispatchClickEvent(spanD); + expect(Scheduler).toHaveYielded(['App']); - expect(Scheduler).toHaveYielded(['App']); + expect(result).toBe(true); - expect(result).toBe(true); + // Continuing rendering will render B next. + expect(Scheduler).toFlushAndYield(['B', 'C']); - // Continuing rendering will render B next. - expect(Scheduler).toFlushAndYield(['B', 'C']); + suspend = false; + resolve(); + await promise; - suspend = false; - resolve(); - await promise; + // After the click, we should prioritize D and the Click first, + // and only after that render A and C. + expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']); - // After the click, we should prioritize D and the Click first, - // and only after that render A and C. - expect(Scheduler).toFlushAndYield(['D', 'Clicked D', 'A']); + document.body.removeChild(container); + }); - document.body.removeChild(container); - }); + it('hydrates at higher pri for secondary discrete events (flare)', async () => { + let usePress = require('react-interactions/events/press').usePress; + let suspend = false; + let resolve; + let promise = new Promise(resolvePromise => (resolve = resolvePromise)); - it('hydrates at higher pri for secondary discrete events (flare)', async () => { - let suspend = false; - let resolve; - let promise = new Promise(resolvePromise => (resolve = resolvePromise)); + function Child({text}) { + if ((text === 'A' || text === 'D') && suspend) { + throw promise; + } + Scheduler.unstable_yieldValue(text); - function Child({text}) { - if ((text === 'A' || text === 'D') && suspend) { - throw promise; + const listener = usePress({ + onPress() { + Scheduler.unstable_yieldValue('Clicked ' + text); + }, + }); + return {text}; } - Scheduler.unstable_yieldValue(text); - const listener = usePress({ - onPress() { - Scheduler.unstable_yieldValue('Clicked ' + text); - }, - }); - return {text}; - } + function App() { + Scheduler.unstable_yieldValue('App'); + return ( +
+ + + + + + + + + + + + +
+ ); + } - function App() { - Scheduler.unstable_yieldValue('App'); - return ( -
- - - - - - - - - - - - -
- ); - } + let finalHTML = ReactDOMServer.renderToString(); - let finalHTML = ReactDOMServer.renderToString(); + expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); - expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']); + let container = document.createElement('div'); + // We need this to be in the document since we'll dispatch events on it. + document.body.appendChild(container); - let container = document.createElement('div'); - // We need this to be in the document since we'll dispatch events on it. - document.body.appendChild(container); + container.innerHTML = finalHTML; - container.innerHTML = finalHTML; + let spanA = container.getElementsByTagName('span')[0]; + let spanC = container.getElementsByTagName('span')[2]; + let spanD = container.getElementsByTagName('span')[3]; - let spanA = container.getElementsByTagName('span')[0]; - let spanC = container.getElementsByTagName('span')[2]; - let spanD = container.getElementsByTagName('span')[3]; + suspend = true; - suspend = true; + // A and D will be suspended. We'll click on D which should take + // priority, after we unsuspend. + let root = ReactDOM.createRoot(container, {hydrate: true}); + root.render(); - // A and D will be suspended. We'll click on D which should take - // priority, after we unsuspend. - let root = ReactDOM.createRoot(container, {hydrate: true}); - root.render(); + // Nothing has been hydrated so far. + expect(Scheduler).toHaveYielded([]); - // Nothing has been hydrated so far. - expect(Scheduler).toHaveYielded([]); + // This click target cannot be hydrated yet because the first is Suspended. + dispatchClickEvent(spanA); + dispatchClickEvent(spanC); + dispatchClickEvent(spanD); - // This click target cannot be hydrated yet because the first is Suspended. - dispatchClickEvent(spanA); - dispatchClickEvent(spanC); - dispatchClickEvent(spanD); + expect(Scheduler).toHaveYielded(['App']); - expect(Scheduler).toHaveYielded(['App']); + suspend = false; + resolve(); + await promise; - suspend = false; - resolve(); - await promise; - - // We should prioritize hydrating A, C and D first since we clicked in - // them. Only after they're done will we hydrate B. - expect(Scheduler).toFlushAndYield([ - 'A', - 'Clicked A', - 'C', - 'Clicked C', - 'D', - 'Clicked D', - // B should render last since it wasn't clicked. - 'B', - ]); + // We should prioritize hydrating A, C and D first since we clicked in + // them. Only after they're done will we hydrate B. + expect(Scheduler).toFlushAndYield([ + 'A', + 'Clicked A', + 'C', + 'Clicked C', + 'D', + 'Clicked D', + // B should render last since it wasn't clicked. + 'B', + ]); - document.body.removeChild(container); - }); + document.body.removeChild(container); + }); + } it('hydrates the hovered targets as higher priority for continuous events', async () => { let suspend = false; diff --git a/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js b/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js index 2a540f9929d14..678f96de9f080 100644 --- a/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js +++ b/packages/react-dom/src/__tests__/renderSubtreeIntoContainer-test.js @@ -19,7 +19,7 @@ const renderSubtreeIntoContainer = require('react-dom') const ReactFeatureFlags = require('shared/ReactFeatureFlags'); // Once this flag is always true, we should delete this test file -if (ReactFeatureFlags.disableUnstableRenderSubtreeIntoContainer) { +if (__EXPERIMENTAL__) { describe('renderSubtreeIntoContainer', () => { it('empty test', () => { // Empty test to prevent "Your test suite must contain at least one test." error. diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 791fc098293a1..340cba54773e3 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -35,7 +35,6 @@ import { attemptUserBlockingHydration, attemptContinuousHydration, attemptHydrationAtCurrentPriority, - act, } from 'react-reconciler/inline.dom'; import {createPortal as createPortalImpl} from 'shared/ReactPortal'; import {canUseDOM} from 'shared/ExecutionEnvironment'; @@ -56,14 +55,7 @@ import { } from 'legacy-events/EventPropagators'; import ReactVersion from 'shared/ReactVersion'; import invariant from 'shared/invariant'; -import { - exposeConcurrentModeAPIs, - disableLegacyReactDOMAPIs, - disableUnstableCreatePortal, - disableUnstableRenderSubtreeIntoContainer, - warnUnstableRenderSubtreeIntoContainer, - isTestEnvironment, -} from 'shared/ReactFeatureFlags'; +import {warnUnstableRenderSubtreeIntoContainer} from 'shared/ReactFeatureFlags'; import { getInstanceFromNode, @@ -120,104 +112,113 @@ function createPortal( children: ReactNodeList, container: Container, key: ?string = null, -) { +): React$Portal { invariant( isValidContainer(container), 'Target container is not a DOM element.', ); // TODO: pass ReactDOM portal implementation as third argument + // $FlowFixMe The Flow type is opaque but there's no way to actually create it. return createPortalImpl(children, container, null, key); } -const ReactDOM: Object = { - createPortal, - - unstable_batchedUpdates: batchedUpdates, - - flushSync: flushSync, - - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { - // Keep in sync with ReactDOMUnstableNativeDependencies.js - // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification. - Events: [ - getInstanceFromNode, - getNodeFromInstance, - getFiberCurrentPropsFromNode, - injectEventPluginsByName, - eventNameDispatchConfigs, - accumulateTwoPhaseDispatches, - accumulateDirectDispatches, - enqueueStateRestore, - restoreStateIfNeeded, - dispatchEvent, - runEventsInBatch, - flushPassiveEffects, - IsThisRendererActing, - ], - }, - - version: ReactVersion, -}; - -if (!disableLegacyReactDOMAPIs) { - ReactDOM.findDOMNode = findDOMNode; - ReactDOM.hydrate = hydrate; - ReactDOM.render = render; - ReactDOM.unmountComponentAtNode = unmountComponentAtNode; +function scheduleHydration(target: Node) { + if (target) { + queueExplicitHydrationTarget(target); + } } -if (exposeConcurrentModeAPIs) { - ReactDOM.createRoot = createRoot; - ReactDOM.createBlockingRoot = createBlockingRoot; - - ReactDOM.unstable_discreteUpdates = discreteUpdates; - ReactDOM.unstable_flushDiscreteUpdates = flushDiscreteUpdates; - ReactDOM.unstable_flushControlled = flushControlled; - - ReactDOM.unstable_scheduleHydration = target => { - if (target) { - queueExplicitHydrationTarget(target); +function renderSubtreeIntoContainer( + parentComponent: React$Component, + element: React$Element, + containerNode: Container, + callback: ?Function, +) { + if (__DEV__) { + if ( + warnUnstableRenderSubtreeIntoContainer && + !didWarnAboutUnstableRenderSubtreeIntoContainer + ) { + didWarnAboutUnstableRenderSubtreeIntoContainer = true; + console.warn( + 'ReactDOM.unstable_renderSubtreeIntoContainer() is deprecated ' + + 'and will be removed in a future major release. Consider using ' + + 'React Portals instead.', + ); } - }; + } + return unstable_renderSubtreeIntoContainer( + parentComponent, + element, + containerNode, + callback, + ); } -if (!disableUnstableRenderSubtreeIntoContainer) { - ReactDOM.unstable_renderSubtreeIntoContainer = function(...args) { - if (__DEV__) { - if ( - warnUnstableRenderSubtreeIntoContainer && - !didWarnAboutUnstableRenderSubtreeIntoContainer - ) { - didWarnAboutUnstableRenderSubtreeIntoContainer = true; - console.warn( - 'ReactDOM.unstable_renderSubtreeIntoContainer() is deprecated ' + - 'and will be removed in a future major release. Consider using ' + - 'React Portals instead.', - ); - } +function unstable_createPortal( + children: ReactNodeList, + container: Container, + key: ?string = null, +) { + if (__DEV__) { + if (!didWarnAboutUnstableCreatePortal) { + didWarnAboutUnstableCreatePortal = true; + console.warn( + 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' + + 'and will be removed in React 17+. Update your code to use ' + + 'ReactDOM.createPortal() instead. It has the exact same API, ' + + 'but without the "unstable_" prefix.', + ); } - return unstable_renderSubtreeIntoContainer(...args); - }; + } + return createPortal(children, container, key); } -if (!disableUnstableCreatePortal) { +const Internals = { + // Keep in sync with ReactDOMUnstableNativeDependencies.js + // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification. + Events: [ + getInstanceFromNode, + getNodeFromInstance, + getFiberCurrentPropsFromNode, + injectEventPluginsByName, + eventNameDispatchConfigs, + accumulateTwoPhaseDispatches, + accumulateDirectDispatches, + enqueueStateRestore, + restoreStateIfNeeded, + dispatchEvent, + runEventsInBatch, + flushPassiveEffects, + IsThisRendererActing, + ], +}; + +export { + createPortal, + batchedUpdates as unstable_batchedUpdates, + flushSync, + Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + ReactVersion as version, + // Disabled behind disableLegacyReactDOMAPIs + findDOMNode, + hydrate, + render, + unmountComponentAtNode, + // exposeConcurrentModeAPIs + createRoot, + createBlockingRoot, + discreteUpdates as unstable_discreteUpdates, + flushDiscreteUpdates as unstable_flushDiscreteUpdates, + flushControlled as unstable_flushControlled, + scheduleHydration as unstable_scheduleHydration, + // Disabled behind disableUnstableRenderSubtreeIntoContainer + renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer, + // Disabled behind disableUnstableCreatePortal // Temporary alias since we already shipped React 16 RC with it. // TODO: remove in React 17. - ReactDOM.unstable_createPortal = function(...args) { - if (__DEV__) { - if (!didWarnAboutUnstableCreatePortal) { - didWarnAboutUnstableCreatePortal = true; - console.warn( - 'The ReactDOM.unstable_createPortal() alias has been deprecated, ' + - 'and will be removed in React 17+. Update your code to use ' + - 'ReactDOM.createPortal() instead. It has the exact same API, ' + - 'but without the "unstable_" prefix.', - ); - } - } - return createPortal(...args); - }; -} + unstable_createPortal, +}; const foundDevTools = injectIntoDevTools({ findFiberByHostInstance: getClosestInstanceFromNode, @@ -252,9 +253,3 @@ if (__DEV__) { } } } - -if (isTestEnvironment) { - ReactDOM.act = act; -} - -export default ReactDOM; diff --git a/packages/react-dom/src/client/ReactDOMFB.js b/packages/react-dom/src/client/ReactDOMFB.js deleted file mode 100644 index 02403919e77dd..0000000000000 --- a/packages/react-dom/src/client/ReactDOMFB.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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 {addUserTimingListener} from 'shared/ReactFeatureFlags'; - -import ReactDOM from './ReactDOM'; -import {isEnabled} from '../events/ReactDOMEventListener'; -import {getClosestInstanceFromNode} from './ReactDOMComponentTree'; - -if (__EXPERIMENTAL__) { - // This is a modern WWW build. - // It should be the same as open source. Don't add new things here. -} else { - // For classic WWW builds, include a few internals that are already in use. - Object.assign( - (ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: any), - { - ReactBrowserEventEmitter: { - isEnabled, - }, - ReactDOMComponentTree: { - getClosestInstanceFromNode, - }, - // Perf experiment - addUserTimingListener, - }, - ); -} - -export default ReactDOM; diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index f15805cee7470..c61aa20c85836 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -101,8 +101,8 @@ export type EventTargetChildElement = { ... }; export type Container = - | (Element & {_reactRootContainer: ?RootType, ...}) - | (Document & {_reactRootContainer: ?RootType, ...}); + | (Element & {_reactRootContainer?: RootType, ...}) + | (Document & {_reactRootContainer?: RootType, ...}); export type Instance = Element; export type TextInstance = Text; export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...}; diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index d074ecf577ec6..97fbac293578a 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -370,6 +370,7 @@ export function unmountComponentAtNode(container: Container) { // Unmount should not be batched. unbatchedUpdates(() => { legacyRenderSubtreeIntoContainer(null, null, container, false, () => { + // $FlowFixMe This should probably use `delete container._reactRootContainer` container._reactRootContainer = null; unmarkContainerAsRoot(container); }); diff --git a/packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js index 3711cbe30365b..c3fa398e83766 100644 --- a/packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js @@ -66,6 +66,11 @@ function dispatchClickEvent(element) { describe('DOMEventResponderSystem', () => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { jest.resetModules(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); diff --git a/packages/react-dom/testing.classic.fb.js b/packages/react-dom/testing.classic.fb.js new file mode 100644 index 0000000000000..841f6a1d8f8db --- /dev/null +++ b/packages/react-dom/testing.classic.fb.js @@ -0,0 +1,11 @@ +/** + * 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 + */ + +export * from './index.classic.fb.js'; +export {act} from 'react-reconciler/inline.dom'; diff --git a/packages/react-dom/testing.experimental.js b/packages/react-dom/testing.experimental.js new file mode 100644 index 0000000000000..0727208336ccd --- /dev/null +++ b/packages/react-dom/testing.experimental.js @@ -0,0 +1,11 @@ +/** + * 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 + */ + +export * from './index.experimental.js'; +export {act} from 'react-reconciler/inline.dom'; diff --git a/packages/react-dom/testing.js b/packages/react-dom/testing.js index 2a016ba16e9db..6169f8727267a 100644 --- a/packages/react-dom/testing.js +++ b/packages/react-dom/testing.js @@ -7,10 +7,5 @@ * @flow */ -'use strict'; - -const ReactDOM = require('./src/client/ReactDOM'); - -// TODO: decide on the top-level export form. -// This is hacky but makes it work with both Rollup and Jest. -module.exports = ReactDOM.default || ReactDOM; +export * from './index.js'; +export {act} from 'react-reconciler/inline.dom'; diff --git a/packages/react-dom/testing.modern.fb.js b/packages/react-dom/testing.modern.fb.js new file mode 100644 index 0000000000000..4eca948d49902 --- /dev/null +++ b/packages/react-dom/testing.modern.fb.js @@ -0,0 +1,11 @@ +/** + * 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 + */ + +export * from './index.modern.fb.js'; +export {act} from 'react-reconciler/inline.dom'; diff --git a/packages/react-dom/testing.stable.js b/packages/react-dom/testing.stable.js new file mode 100644 index 0000000000000..474972b70d816 --- /dev/null +++ b/packages/react-dom/testing.stable.js @@ -0,0 +1,11 @@ +/** + * 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 + */ + +export * from './index.stable.js'; +export {act} from 'react-reconciler/inline.dom'; diff --git a/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js index f57d416459776..affcf8e6fbfa5 100644 --- a/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js @@ -38,6 +38,11 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)('ContextMenu responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js index 66946f07abcce..0b751d5c063dc 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js @@ -38,6 +38,11 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)('Focus responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js index 647527e4e4417..6d474b429bd2e 100644 --- a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js @@ -37,6 +37,11 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)('FocusWithin responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js index 06437852b45c9..35f71ebabafee 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js @@ -34,6 +34,11 @@ const table = [[forcePointerEvents], [!forcePointerEvents]]; describe.each(table)('Hover responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js index cf1bb6670f57d..5248e58a067f0 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js @@ -45,6 +45,11 @@ const modulesInit = () => { describe('Input event responder', () => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { jest.resetModules(); modulesInit(); diff --git a/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js index 68a69dfba6c52..bd649765d5e60 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js @@ -28,6 +28,11 @@ function initializeModules(hasPointerEvents) { describe('Keyboard responder', () => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js b/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js index aeb265329ed27..6dc3ee24ec7f6 100644 --- a/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js @@ -19,6 +19,11 @@ let Scheduler; describe('mixing responders with the heritage event system', () => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.enableDeprecatedFlareAPI = true; diff --git a/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js index 6987280090aae..e9245fbb53bac 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js @@ -38,6 +38,11 @@ const pointerTypesTable = [['mouse'], ['touch']]; describeWithPointerEvent('Press responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js index 65cb1141f8b11..381fed4a7fad1 100644 --- a/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js @@ -50,6 +50,11 @@ const pointerTypesTable = [['mouse'], ['touch']]; describe.each(environmentTable)('Press responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js index 39fd5d06f9beb..17c550a24836e 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js @@ -73,6 +73,11 @@ function tapAndReleaseOutside({ describeWithPointerEvent('Tap responder', hasPointerEvents => { let container; + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + beforeEach(() => { initializeModules(hasPointerEvents); container = document.createElement('div'); diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js index c14ecaf51e1ee..a56c9f756a1d3 100644 --- a/packages/react-is/src/__tests__/ReactIs-test.js +++ b/packages/react-is/src/__tests__/ReactIs-test.js @@ -13,8 +13,6 @@ let React; let ReactDOM; let ReactIs; -const ReactFeatureFlags = require('shared/ReactFeatureFlags'); - describe('ReactIs', () => { beforeEach(() => { jest.resetModules(); @@ -56,7 +54,7 @@ describe('ReactIs', () => { expect(ReactIs.isValidElementType(MemoComponent)).toEqual(true); expect(ReactIs.isValidElementType(Context.Provider)).toEqual(true); expect(ReactIs.isValidElementType(Context.Consumer)).toEqual(true); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { let factory; expect(() => { factory = React.createFactory('div'); diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index 13ae10b1f6276..013a4ec77ea4e 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -9,6 +9,7 @@ import type {ReactFabricType, HostComponent} from './ReactNativeTypes'; import type {ReactNodeList} from 'shared/ReactTypes'; +import type {ElementRef} from 'react'; import './ReactFabricInjection'; @@ -43,7 +44,7 @@ const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; function findHostInstance_DEPRECATED( componentOrHandle: any, -): ?React$ElementRef> { +): ?ElementRef> { if (__DEV__) { const owner = ReactCurrentOwner.current; if (owner !== null && owner.stateNode !== null) { diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 6e371f9ff8cd0..4f1b140f8f809 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -7,7 +7,9 @@ * @flow */ +import type {ElementRef} from 'react'; import type { + HostComponent, MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, @@ -126,7 +128,7 @@ class ReactFabricHostComponent { } measureLayout( - relativeToNativeNode: number | ReactFabricHostComponent, + relativeToNativeNode: number | ElementRef>, onSuccess: MeasureLayoutOnSuccessCallback, onFail?: () => void /* currently unused */, ) { diff --git a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js index c4c74462a4d7e..f695971f4db49 100644 --- a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js +++ b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js @@ -7,7 +7,9 @@ * @flow */ +import type {ElementRef} from 'react'; import type { + HostComponent, MeasureInWindowOnSuccessCallback, MeasureLayoutOnSuccessCallback, MeasureOnSuccessCallback, @@ -62,7 +64,7 @@ class ReactNativeFiberHostComponent { } measureLayout( - relativeToNativeNode: number | ReactNativeFiberHostComponent, + relativeToNativeNode: number | ElementRef>, onSuccess: MeasureLayoutOnSuccessCallback, onFail?: () => void /* currently unused */, ) { @@ -71,8 +73,11 @@ class ReactNativeFiberHostComponent { if (typeof relativeToNativeNode === 'number') { // Already a node handle relativeNode = relativeToNativeNode; - } else if (relativeToNativeNode._nativeTag) { - relativeNode = relativeToNativeNode._nativeTag; + } else { + let nativeNode: ReactNativeFiberHostComponent = (relativeToNativeNode: any); + if (nativeNode._nativeTag) { + relativeNode = nativeNode._nativeTag; + } } if (relativeNode == null) { diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js index e91c16a4909dc..0349591a99350 100644 --- a/packages/react-native-renderer/src/ReactNativeHostConfig.js +++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js @@ -7,8 +7,6 @@ * @flow */ -import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes'; - import invariant from 'shared/invariant'; // Modules provided by RN: @@ -31,12 +29,7 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry; export type Type = string; export type Props = Object; export type Container = number; -export type Instance = { - _children: Array, - _nativeTag: number, - viewConfig: ReactNativeBaseComponentViewConfig<>, - ... -}; +export type Instance = ReactNativeFiberHostComponent; export type TextInstance = number; export type HydratableInstance = Instance | TextInstance; export type PublicInstance = Instance; diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 7619625b1371f..48f89b7d858b3 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -126,7 +126,9 @@ export type ReactNativeType = { }; export type ReactFabricType = { - findHostInstance_DEPRECATED(componentOrHandle: any): ?HostComponent, + findHostInstance_DEPRECATED( + componentOrHandle: any, + ): ?ElementRef>, findNodeHandle(componentOrHandle: any): ?number, dispatchCommand(handle: any, command: string, args: Array): void, render( diff --git a/packages/react-reconciler/src/__tests__/ReactFiberFundamental-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberFundamental-test.internal.js index 00b00b533b95f..081b0be2d38fa 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberFundamental-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberFundamental-test.internal.js @@ -50,6 +50,11 @@ function initReactDOMServer() { } describe('ReactFiberFundamental', () => { + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + describe('NoopRenderer', () => { beforeEach(() => { initNoopRenderer(); diff --git a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js index 8144a11be827c..78b863e995166 100644 --- a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js @@ -23,6 +23,11 @@ describe('ReactScope', () => { React = require('react'); }); + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + describe('ReactDOM', () => { let ReactDOM; let container; diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js new file mode 100644 index 0000000000000..e0d720efe846d --- /dev/null +++ b/packages/react/index.classic.fb.js @@ -0,0 +1,55 @@ +/** + * 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 + */ + +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useDebugValue, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + Fragment, + Profiler, + StrictMode, + Suspense, + createElement, + cloneElement, + isValidElement, + version, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + createFactory, + // exposeConcurrentModeAPIs + useTransition, + useDeferredValue, + SuspenseList, + unstable_withSuspenseConfig, + // enableBlocksAPI + block, + // enableDeprecatedFlareAPI + DEPRECATED_useResponder, + DEPRECATED_createResponder, + // enableScopeAPI + unstable_createScope, + // enableJSXTransformAPI + jsx, + jsxs, + jsxDEV, +} from './src/React'; diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js new file mode 100644 index 0000000000000..e348f332b7f76 --- /dev/null +++ b/packages/react/index.experimental.js @@ -0,0 +1,46 @@ +/** + * 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 + */ + +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useDebugValue, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + Fragment, + Profiler, + StrictMode, + Suspense, + createElement, + cloneElement, + isValidElement, + version, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + createFactory, + // exposeConcurrentModeAPIs + useTransition, + useDeferredValue, + SuspenseList, + unstable_withSuspenseConfig, + // enableBlocksAPI + block, +} from './src/React'; diff --git a/packages/react/index.js b/packages/react/index.js index 4268898c088b9..c0dd6bbdb3c35 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -7,10 +7,74 @@ * @flow */ -'use strict'; +// Keep in sync with https://github.com/facebook/flow/blob/master/lib/react.js +export type StatelessFunctionalComponent< + P, +> = React$StatelessFunctionalComponent

; +export type ComponentType<-P> = React$ComponentType

; +export type AbstractComponent< + -Config, + +Instance = mixed, +> = React$AbstractComponent; +export type ElementType = React$ElementType; +export type Element<+C> = React$Element; +export type Key = React$Key; +export type Ref = React$Ref; +export type Node = React$Node; +export type Context = React$Context; +export type Portal = React$Portal; +export type ElementProps = React$ElementProps; +export type ElementConfig = React$ElementConfig; +export type ElementRef = React$ElementRef; +export type Config = React$Config; +export type ChildrenArray<+T> = $ReadOnlyArray> | T; +export type Interaction = { + name: string, + timestamp: number, + ... +}; -const React = require('./src/React'); - -// TODO: decide on the top-level export form. -// This is hacky but makes it work with both Rollup and Jest. -module.exports = React.default || React; +// Export all exports so that they're available in tests. +// We can't use export * from in Flow for some reason. +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useDebugValue, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + Fragment, + Profiler, + StrictMode, + Suspense, + createElement, + cloneElement, + isValidElement, + version, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + createFactory, + useTransition, + useDeferredValue, + SuspenseList, + unstable_withSuspenseConfig, + block, + DEPRECATED_useResponder, + DEPRECATED_createResponder, + unstable_createFundamental, + unstable_createScope, + jsx, + jsxs, + jsxDEV, +} from './src/React'; diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js new file mode 100644 index 0000000000000..0c6ba986e2e08 --- /dev/null +++ b/packages/react/index.modern.fb.js @@ -0,0 +1,54 @@ +/** + * 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 + */ + +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useDebugValue, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + Fragment, + Profiler, + StrictMode, + Suspense, + createElement, + cloneElement, + isValidElement, + version, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + // exposeConcurrentModeAPIs + useTransition, + useDeferredValue, + SuspenseList, + unstable_withSuspenseConfig, + // enableBlocksAPI + block, + // enableDeprecatedFlareAPI + DEPRECATED_useResponder, + DEPRECATED_createResponder, + // enableScopeAPI + unstable_createScope, + // enableJSXTransformAPI + jsx, + jsxs, + jsxDEV, +} from './src/React'; diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js new file mode 100644 index 0000000000000..67a221ca674b7 --- /dev/null +++ b/packages/react/index.stable.js @@ -0,0 +1,39 @@ +/** + * 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 + */ + +export { + Children, + createRef, + Component, + PureComponent, + createContext, + forwardRef, + lazy, + memo, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useDebugValue, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState, + Fragment, + Profiler, + StrictMode, + Suspense, + createElement, + cloneElement, + isValidElement, + version, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + createFactory, +} from './src/React'; diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 23fb0f87917b6..3fd7304f99398 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -18,11 +18,11 @@ import {Component, PureComponent} from './ReactBaseClasses'; import {createRef} from './ReactCreateRef'; import {forEach, map, count, toArray, only} from './ReactChildren'; import { - createElement, - createFactory, - cloneElement, + createElement as createElementProd, + createFactory as createFactoryProd, + cloneElement as cloneElementProd, isValidElement, - jsx, + jsx as jsxProd, } from './ReactElement'; import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; @@ -57,33 +57,35 @@ import ReactSharedInternals from './ReactSharedInternals'; import createFundamental from 'shared/createFundamentalComponent'; import createResponder from 'shared/createEventResponder'; import createScope from 'shared/createScope'; -import { - enableJSXTransformAPI, - enableDeprecatedFlareAPI, - enableFundamentalAPI, - enableScopeAPI, - exposeConcurrentModeAPIs, - enableBlocksAPI, - disableCreateFactory, -} from 'shared/ReactFeatureFlags'; -const React = { - Children: { - map, - forEach, - count, - toArray, - only, - }, +// TODO: Move this branching into the other module instead and just re-export. +const createElement = __DEV__ ? createElementWithValidation : createElementProd; +const cloneElement = __DEV__ ? cloneElementWithValidation : cloneElementProd; +const createFactory = __DEV__ ? createFactoryWithValidation : createFactoryProd; + +const jsxDEV = __DEV__ ? jsxWithValidation : undefined; +const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd; +// we may want to special case jsxs internally to take advantage of static children. +// for now we can ship identical prod functions +const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd; + +const Children = { + map, + forEach, + count, + toArray, + only, +}; + +export { + Children, createRef, Component, PureComponent, - createContext, forwardRef, lazy, memo, - useCallback, useContext, useEffect, @@ -94,65 +96,34 @@ const React = { useReducer, useRef, useState, - - Fragment: REACT_FRAGMENT_TYPE, - Profiler: REACT_PROFILER_TYPE, - StrictMode: REACT_STRICT_MODE_TYPE, - Suspense: REACT_SUSPENSE_TYPE, - - createElement: __DEV__ ? createElementWithValidation : createElement, - cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, - isValidElement: isValidElement, - - version: ReactVersion, - - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals, + REACT_FRAGMENT_TYPE as Fragment, + REACT_PROFILER_TYPE as Profiler, + REACT_STRICT_MODE_TYPE as StrictMode, + REACT_SUSPENSE_TYPE as Suspense, + createElement, + cloneElement, + isValidElement, + ReactVersion as version, + ReactSharedInternals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, + // Deprecated behind disableCreateFactory + createFactory, + // Concurrent Mode + useTransition, + useDeferredValue, + REACT_SUSPENSE_LIST_TYPE as SuspenseList, + withSuspenseConfig as unstable_withSuspenseConfig, + // enableBlocksAPI + block, + // enableDeprecatedFlareAPI + useResponder as DEPRECATED_useResponder, + createResponder as DEPRECATED_createResponder, + // enableFundamentalAPI + createFundamental as unstable_createFundamental, + // enableScopeAPI + createScope as unstable_createScope, + // enableJSXTransformAPI + jsx, + jsxs, + // TODO: jsxDEV should not be exposed as a name. We might want to move it to a different entry point. + jsxDEV, }; - -if (!disableCreateFactory) { - React.createFactory = __DEV__ ? createFactoryWithValidation : createFactory; -} - -if (exposeConcurrentModeAPIs) { - React.useTransition = useTransition; - React.useDeferredValue = useDeferredValue; - React.SuspenseList = REACT_SUSPENSE_LIST_TYPE; - React.unstable_withSuspenseConfig = withSuspenseConfig; -} - -if (enableBlocksAPI) { - React.block = block; -} - -if (enableDeprecatedFlareAPI) { - React.DEPRECATED_useResponder = useResponder; - React.DEPRECATED_createResponder = createResponder; -} - -if (enableFundamentalAPI) { - React.unstable_createFundamental = createFundamental; -} - -if (enableScopeAPI) { - React.unstable_createScope = createScope; -} - -// Note: some APIs are added with feature flags. -// Make sure that stable builds for open source -// don't modify the React object to avoid deopts. -// Also let's not expose their names in stable builds. - -if (enableJSXTransformAPI) { - if (__DEV__) { - React.jsxDEV = jsxWithValidation; - React.jsx = jsxWithValidationDynamic; - React.jsxs = jsxWithValidationStatic; - } else { - React.jsx = jsx; - // we may want to special case jsxs internally to take advantage of static children. - // for now we can ship identical prod functions - React.jsxs = jsx; - } -} - -export default React; diff --git a/packages/react/src/__tests__/ReactElement-test.js b/packages/react/src/__tests__/ReactElement-test.js index af99767220a1c..6c3d4f6cebae0 100644 --- a/packages/react/src/__tests__/ReactElement-test.js +++ b/packages/react/src/__tests__/ReactElement-test.js @@ -13,8 +13,6 @@ let React; let ReactDOM; let ReactTestUtils; -const ReactFeatureFlags = require('shared/ReactFeatureFlags'); - describe('ReactElement', () => { let ComponentClass; let originalSymbol; @@ -312,7 +310,7 @@ describe('ReactElement', () => { expect(React.isValidElement(true)).toEqual(false); expect(React.isValidElement({})).toEqual(false); expect(React.isValidElement('string')).toEqual(false); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { let factory; expect(() => { factory = React.createFactory('div'); @@ -481,7 +479,7 @@ describe('ReactElement', () => { expect(React.isValidElement(true)).toEqual(false); expect(React.isValidElement({})).toEqual(false); expect(React.isValidElement('string')).toEqual(false); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { let factory; expect(() => { factory = React.createFactory('div'); diff --git a/packages/react/src/__tests__/ReactElementJSX-test.internal.js b/packages/react/src/__tests__/ReactElementJSX-test.internal.js index cb45ed714b78f..4a02bbc1a379d 100644 --- a/packages/react/src/__tests__/ReactElementJSX-test.internal.js +++ b/packages/react/src/__tests__/ReactElementJSX-test.internal.js @@ -31,7 +31,6 @@ describe('ReactElement.jsx', () => { global.Symbol = undefined; ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableJSXTransformAPI = true; ReactFeatureFlags.warnAboutSpreadingKeyToJSX = true; React = require('react'); @@ -43,6 +42,11 @@ describe('ReactElement.jsx', () => { global.Symbol = originalSymbol; }); + if (!__EXPERIMENTAL__) { + it("empty test so Jest doesn't complain", () => {}); + return; + } + it('allows static methods to be called using the type property', () => { class StaticMethodComponentClass extends React.Component { render() { @@ -69,7 +73,7 @@ describe('ReactElement.jsx', () => { expect(React.isValidElement(true)).toEqual(false); expect(React.isValidElement({})).toEqual(false); expect(React.isValidElement('string')).toEqual(false); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { let factory; expect(() => { factory = React.createFactory('div'); @@ -292,9 +296,6 @@ describe('ReactElement.jsx', () => { jest.resetModules(); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableJSXTransformAPI = true; - React = require('react'); class Component extends React.Component { @@ -310,7 +311,7 @@ describe('ReactElement.jsx', () => { expect(React.isValidElement(true)).toEqual(false); expect(React.isValidElement({})).toEqual(false); expect(React.isValidElement('string')).toEqual(false); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { let factory; expect(() => { factory = React.createFactory('div'); diff --git a/packages/react/src/__tests__/ReactElementValidator-test.internal.js b/packages/react/src/__tests__/ReactElementValidator-test.internal.js index c4016387e159e..9380998460c11 100644 --- a/packages/react/src/__tests__/ReactElementValidator-test.internal.js +++ b/packages/react/src/__tests__/ReactElementValidator-test.internal.js @@ -439,7 +439,7 @@ describe('ReactElementValidator', () => { ); }); - if (!ReactFeatureFlags.disableCreateFactory) { + if (!__EXPERIMENTAL__) { it('should warn when accessing .type on an element factory', () => { function TestComponent() { return

; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index eab16bb07932d..784accd974c29 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -44,10 +44,6 @@ export function addUserTimingListener() { // Disable javascript: URL strings in href for XSS protection. export const disableJavaScriptURLs = false; -// These APIs will no longer be "unstable" in the upcoming 16.7 release, -// Control this behavior with a flag to support 16.6 minor releases in the meanwhile. -export const exposeConcurrentModeAPIs = __EXPERIMENTAL__; - // Warns when a combination of updates on a dom can cause a style declaration // that clashes with a previous one https://github.com/facebook/react/pull/14181 export const warnAboutShorthandPropertyCollision = true; @@ -62,7 +58,6 @@ export const enableFundamentalAPI = false; export const enableScopeAPI = false; // New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107 -export const enableJSXTransformAPI = false; // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?) // Till then, we warn about the missing mock, but still fallback to a legacy mode compatible version @@ -108,10 +103,6 @@ export const runAllPassiveEffectDestroysBeforeCreates = false; // WARNING This flag only has an affect if used with runAllPassiveEffectDestroysBeforeCreates. export const deferPassiveEffectCleanupDuringUnmount = false; -// Use this flag to generate "testing" builds, that include APIs like act() -// and extra warnings/errors -export const isTestEnvironment = false; - // Enables a warning when trying to spread a 'key' to an element; // a deprecated pattern we want to get rid of in the future export const warnAboutSpreadingKeyToJSX = false; @@ -128,25 +119,14 @@ export const warnAboutStringRefs = false; export const disableLegacyContext = false; -// Disables React.createFactory -export const disableCreateFactory = false; - -// Disables hydrate, render, findDOMNode, unmountComponentAtNode -export const disableLegacyReactDOMAPIs = false; - // Disables children for