From 7ef99be497b9ceafba57ce21549b5e3a49b6033a Mon Sep 17 00:00:00 2001 From: Eddie Kimmel Date: Fri, 26 Aug 2022 11:19:42 -0700 Subject: [PATCH] [change] React 18 support * Support React 18 concurrency and constraints. * Add new render / hydrate functions. * Remove uses of findNodeHandle. * Expose ability to unmount an application once ran. Fix #1529 Close #2330 --- configs/.eslintrc | 2 +- package-lock.json | 125 ++++++------ package.json | 2 +- packages/benchmarks/src/index.js | 5 +- .../src/pages/docs/apis/app-registry.md | 4 + .../react-native-web-examples/package.json | 4 +- .../pages/app-registry/index.js | 8 +- packages/react-native-web/package.json | 4 +- .../ActivityIndicator/__tests__/index-test.js | 3 +- .../src/exports/AppRegistry/AppContainer.js | 54 +++-- .../AppRegistry/__tests__/index-test.js | 59 +++++- .../src/exports/AppRegistry/index.js | 8 +- .../exports/AppRegistry/renderApplication.js | 30 ++- .../exports/Button/__tests__/index-test.js | 3 +- .../exports/CheckBox/__tests__/index-test.js | 3 +- .../src/exports/Image/__tests__/index-test.js | 3 +- .../exports/Pressable/__tests__/index-test.js | 3 +- .../ScrollView/__tests__/index-test.js | 3 +- .../src/exports/Text/__tests__/index-test.js | 3 +- .../exports/TextInput/__tests__/index-test.js | 3 +- .../src/exports/Touchable/index.js | 22 +- .../src/exports/View/__tests__/index-test.js | 3 +- .../src/exports/findNodeHandle/index.js | 4 + .../src/exports/render/index.js | 41 +++- .../src/modules/ScrollResponder/index.js | 16 +- .../createEventHandle/__tests__/index-test.js | 90 ++------ .../__tests__/index-test.node.js | 30 +++ .../modules/useEvent/__tests__/index-test.js | 104 +++------- .../modules/useHover/__tests__/index-test.js | 49 +---- .../useMergeRefs/__tests__/index-test.js | 5 +- .../__tests__/index-test.js | 193 ++++-------------- .../modules/useStable/__tests__/index-test.js | 71 ++----- .../react-native/Animated/AnimatedEvent.js | 10 +- .../Animated/nodes/AnimatedProps.js | 9 +- .../react-native/VirtualizedList/index.js | 3 +- 35 files changed, 392 insertions(+), 587 deletions(-) create mode 100644 packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.node.js diff --git a/configs/.eslintrc b/configs/.eslintrc index a434f948f6..fd03a7a673 100644 --- a/configs/.eslintrc +++ b/configs/.eslintrc @@ -2,7 +2,7 @@ "settings": { "react": { "pragma": "React", - "version": "17.0", + "version": "18.0", "flowVersion": "0.148.0" // Flow version } }, diff --git a/package-lock.json b/package-lock.json index 4bb290bc00..b165ad959e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@babel/preset-env": "^7.18.6", "@babel/preset-flow": "^7.18.6", "@babel/preset-react": "^7.18.6", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^13.3.0", "babel-eslint": "^10.1.0", "babel-jest": "^28.1.2", "babel-plugin-add-module-exports": "^1.0.4", @@ -3103,21 +3103,21 @@ } }, "node_modules/@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.3.0.tgz", + "integrity": "sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" }, "engines": { "node": ">=12" }, "peerDependencies": { - "react": "<18.0.0", - "react-dom": "<18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" } }, "node_modules/@tootallnate/once": { @@ -3366,9 +3366,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.47", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.47.tgz", - "integrity": "sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA==", + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -3377,12 +3377,12 @@ } }, "node_modules/@types/react-dom": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", - "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dev": true, "dependencies": { - "@types/react": "^17" + "@types/react": "*" } }, "node_modules/@types/scheduler": { @@ -12232,30 +12232,26 @@ "license": "ISC" }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "license": "MIT", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "license": "MIT", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.2.0" } }, "node_modules/react-is": { @@ -12899,13 +12895,11 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "license": "MIT", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { @@ -14964,8 +14958,8 @@ "styleq": "^0.1.2" }, "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" } }, "packages/react-native-web-docs": { @@ -15038,8 +15032,8 @@ "dependencies": { "babel-plugin-react-native-web": "0.18.8", "next": "^12.2.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-native-web": "0.18.8" }, "devDependencies": { @@ -17175,14 +17169,14 @@ } }, "@testing-library/react": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", - "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.3.0.tgz", + "integrity": "sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" } }, "@tootallnate/once": { @@ -17415,9 +17409,9 @@ "dev": true }, "@types/react": { - "version": "17.0.47", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.47.tgz", - "integrity": "sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA==", + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", "dev": true, "requires": { "@types/prop-types": "*", @@ -17426,12 +17420,12 @@ } }, "@types/react-dom": { - "version": "17.0.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", - "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dev": true, "requires": { - "@types/react": "^17" + "@types/react": "*" } }, "@types/scheduler": { @@ -23818,22 +23812,20 @@ } }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" } }, "react-is": { @@ -23918,8 +23910,8 @@ "@babel/preset-flow": "^7.18.6", "babel-plugin-react-native-web": "0.18.8", "next": "^12.2.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-native-web": "0.18.8" } }, @@ -24377,12 +24369,11 @@ } }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "schema-utils": { diff --git a/package.json b/package.json index 6e1e23d30e..74b4580af2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@babel/preset-env": "^7.18.6", "@babel/preset-flow": "^7.18.6", "@babel/preset-react": "^7.18.6", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^13.3.0", "babel-eslint": "^10.1.0", "babel-jest": "^28.1.2", "babel-plugin-add-module-exports": "^1.0.4", diff --git a/packages/benchmarks/src/index.js b/packages/benchmarks/src/index.js index def00ac5a0..38dc17d292 100644 --- a/packages/benchmarks/src/index.js +++ b/packages/benchmarks/src/index.js @@ -71,4 +71,7 @@ const tests = { })) }; -ReactDOM.render(, document.querySelector('.root')); +const root = document.querySelector('.root'); +const element = ; + +ReactDOM.createRoot(root).render(element); diff --git a/packages/react-native-web-docs/src/pages/docs/apis/app-registry.md b/packages/react-native-web-docs/src/pages/docs/apis/app-registry.md index 4d38b7bd90..824e1db108 100644 --- a/packages/react-native-web-docs/src/pages/docs/apis/app-registry.md +++ b/packages/react-native-web-docs/src/pages/docs/apis/app-registry.md @@ -69,6 +69,10 @@ If the client should hydrate server-rendered HTML. The initial props passed to the root component. {% endcall %} +{% call macro.prop('mode', '"concurrent" | "legacy"') %} +Default is 'concurrent'. Setting to 'legacy' will make the app will behave as if it’s running React 17. +{% endcall %} + {% call macro.prop('rootTag', 'HTMLElement') %} The native element into which the application is rendered. {% endcall %} diff --git a/packages/react-native-web-examples/package.json b/packages/react-native-web-examples/package.json index d8828d716a..82586634b7 100644 --- a/packages/react-native-web-examples/package.json +++ b/packages/react-native-web-examples/package.json @@ -10,8 +10,8 @@ "dependencies": { "babel-plugin-react-native-web": "0.18.8", "next": "^12.2.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-native-web": "0.18.8" }, "devDependencies": { diff --git a/packages/react-native-web-examples/pages/app-registry/index.js b/packages/react-native-web-examples/pages/app-registry/index.js index 770368a774..53d8f55743 100644 --- a/packages/react-native-web-examples/pages/app-registry/index.js +++ b/packages/react-native-web-examples/pages/app-registry/index.js @@ -25,18 +25,18 @@ export default function AppStatePage() { const iframeRootTag = document.createElement('div'); iframeRootTag.id = 'iframe-root'; iframeBody.appendChild(iframeRootTag); - AppRegistry.runApplication('App', { rootTag: iframeRootTag }); + const app1 = AppRegistry.runApplication('App', { rootTag: iframeRootTag }); const shadowElement = shadowRef.current; const shadowRoot = shadowElement.attachShadow({ mode: 'open' }); const shadowRootTag = document.createElement('div'); shadowRootTag.id = 'shadow-root'; shadowRoot.appendChild(shadowRootTag); - AppRegistry.runApplication('App', { rootTag: shadowRootTag }); + const app2 = AppRegistry.runApplication('App', { rootTag: shadowRootTag }); return () => { - AppRegistry.unmountApplicationComponentAtRootTag(iframeRootTag); - AppRegistry.unmountApplicationComponentAtRootTag(shadowRootTag); + app1.unmount(); + app2.unmount(); }; }, []); diff --git a/packages/react-native-web/package.json b/packages/react-native-web/package.json index 5d5fb131d4..341a06b1aa 100644 --- a/packages/react-native-web/package.json +++ b/packages/react-native-web/package.json @@ -31,8 +31,8 @@ "styleq": "^0.1.2" }, "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "author": "Nicolas Gallagher", "license": "MIT", diff --git a/packages/react-native-web/src/exports/ActivityIndicator/__tests__/index-test.js b/packages/react-native-web/src/exports/ActivityIndicator/__tests__/index-test.js index 885c219852..49b761467f 100644 --- a/packages/react-native-web/src/exports/ActivityIndicator/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/ActivityIndicator/__tests__/index-test.js @@ -7,9 +7,8 @@ import ActivityIndicator from '..'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/ActivityIndicator', () => { describe('prop "accessibilityLabel"', () => { diff --git a/packages/react-native-web/src/exports/AppRegistry/AppContainer.js b/packages/react-native-web/src/exports/AppRegistry/AppContainer.js index 940e6f8681..ff1058a30f 100644 --- a/packages/react-native-web/src/exports/AppRegistry/AppContainer.js +++ b/packages/react-native-web/src/exports/AppRegistry/AppContainer.js @@ -21,30 +21,40 @@ type Props = { const RootTagContext: React.Context = React.createContext(null); -export default function AppContainer(props: Props): React.Node { - const { children, WrapperComponent } = props; - - let innerView = ( - - ); - - if (WrapperComponent) { - innerView = {innerView}; +const AppContainer: React.AbstractComponent = React.forwardRef( + (props: Props, forwardedRef?: React.Ref) => { + const { children, WrapperComponent } = props; + + let innerView = ( + + ); + + if (WrapperComponent) { + innerView = {innerView}; + } + + return ( + + + {innerView} + + + ); } +); - return ( - - - {innerView} - - - ); -} +AppContainer.displayName = 'AppContainer'; + +export default AppContainer; const styles = StyleSheet.create({ appContainer: { diff --git a/packages/react-native-web/src/exports/AppRegistry/__tests__/index-test.js b/packages/react-native-web/src/exports/AppRegistry/__tests__/index-test.js index 2a8d55d8f7..006c0e5d1d 100644 --- a/packages/react-native-web/src/exports/AppRegistry/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/AppRegistry/__tests__/index-test.js @@ -7,10 +7,10 @@ import AppRegistry from '..'; import React from 'react'; - +import { act } from '@testing-library/react'; const NoopComponent = () => React.createElement('div'); -describe('AppRegistry', () => { +describe.each([['concurrent'], ['legacy']])('AppRegistry', (mode) => { describe('runApplication', () => { let rootTag; @@ -27,18 +27,52 @@ describe('AppRegistry', () => { test('callback after render', () => { const callback = jest.fn(); AppRegistry.registerComponent('App', () => NoopComponent); - AppRegistry.runApplication('App', { - initialProps: {}, - rootTag, - callback + act(() => { + AppRegistry.runApplication('App', { + initialProps: {}, + rootTag, + callback, + mode + }); }); expect(callback).toHaveBeenCalledTimes(1); }); + test('unmount ran application', () => { + const setMountedState = jest.fn(); + const MountedStateComponent = () => { + React.useEffect(() => { + setMountedState(true); + return () => { + setMountedState(false); + }; + }, []); + return ; + }; + + AppRegistry.registerComponent('App', () => MountedStateComponent); + let application; + act(() => { + application = AppRegistry.runApplication('App', { + initialProps: {}, + rootTag, + mode + }); + }); + expect(setMountedState).toHaveBeenCalledTimes(1); + expect(setMountedState).toHaveBeenLastCalledWith(true); + act(() => { + application.unmount(); + }); + expect(setMountedState).toHaveBeenCalledTimes(2); + expect(setMountedState).toHaveBeenLastCalledWith(false); + }); + test('styles roots in different documents', () => { AppRegistry.registerComponent('App', () => NoopComponent); - AppRegistry.runApplication('App', { initialProps: {}, rootTag }); - + act(() => { + AppRegistry.runApplication('App', { initialProps: {}, rootTag, mode }); + }); // Create iframe context const iframe = document.createElement('iframe'); document.body.appendChild(iframe); @@ -49,9 +83,12 @@ describe('AppRegistry', () => { // Run in iframe AppRegistry.registerComponent('App', () => NoopComponent); - AppRegistry.runApplication('App', { - initialProps: {}, - rootTag: iframeRootTag + act(() => { + AppRegistry.runApplication('App', { + initialProps: {}, + rootTag: iframeRootTag, + mode + }); }); const iframedoc = iframeRootTag.ownerDocument; diff --git a/packages/react-native-web/src/exports/AppRegistry/index.js b/packages/react-native-web/src/exports/AppRegistry/index.js index 72887eae51..25e70c3a06 100644 --- a/packages/react-native-web/src/exports/AppRegistry/index.js +++ b/packages/react-native-web/src/exports/AppRegistry/index.js @@ -8,6 +8,7 @@ * @flow */ +import type { Application } from './renderApplication'; import type { ComponentType, Node } from 'react'; import invariant from 'fbjs/lib/invariant'; @@ -75,7 +76,7 @@ export default class AppRegistry { appParameters ? appParameters.initialProps : emptyObject, wrapperComponentProvider && wrapperComponentProvider(appParameters) ), - run: (appParameters) => + run: (appParameters): Application => renderApplication( componentProviderInstrumentationHook(componentProvider), wrapperComponentProvider && wrapperComponentProvider(appParameters), @@ -83,6 +84,7 @@ export default class AppRegistry { { hydrate: appParameters.hydrate || false, initialProps: appParameters.initialProps || emptyObject, + mode: appParameters.mode || 'concurrent', rootTag: appParameters.rootTag } ) @@ -107,7 +109,7 @@ export default class AppRegistry { return appKey; } - static runApplication(appKey: string, appParameters: Object): void { + static runApplication(appKey: string, appParameters: Object): Application { const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'; if (isDevelopment) { @@ -128,7 +130,7 @@ export default class AppRegistry { 'This is either due to an import error during initialization or failure to call AppRegistry.registerComponent.' ); - runnables[appKey].run(appParameters); + return runnables[appKey].run(appParameters); } static setComponentProviderInstrumentationHook( diff --git a/packages/react-native-web/src/exports/AppRegistry/renderApplication.js b/packages/react-native-web/src/exports/AppRegistry/renderApplication.js index 7e2af045b2..95df521336 100644 --- a/packages/react-native-web/src/exports/AppRegistry/renderApplication.js +++ b/packages/react-native-web/src/exports/AppRegistry/renderApplication.js @@ -12,10 +12,14 @@ import type { ComponentType, Node } from 'react'; import AppContainer from './AppContainer'; import invariant from 'fbjs/lib/invariant'; -import render, { hydrate } from '../render'; +import renderLegacy, { hydrateLegacy, render, hydrate } from '../render'; import StyleSheet from '../StyleSheet'; import React from 'react'; +export type Application = { + unmount: () => void +}; + export default function renderApplication( RootComponent: ComponentType, WrapperComponent?: ?ComponentType<*>, @@ -23,20 +27,30 @@ export default function renderApplication( options: { hydrate: boolean, initialProps: Props, + mode: 'concurrent' | 'legacy', rootTag: any } -) { - const { hydrate: shouldHydrate, initialProps, rootTag } = options; - const renderFn = shouldHydrate ? hydrate : render; +): Application { + const { hydrate: shouldHydrate, initialProps, mode, rootTag } = options; + const renderFn = shouldHydrate + ? mode === 'concurrent' + ? hydrate + : hydrateLegacy + : mode === 'concurrent' + ? render + : renderLegacy; invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag); - renderFn( - + return renderFn( + , - rootTag, - callback + rootTag ); } diff --git a/packages/react-native-web/src/exports/Button/__tests__/index-test.js b/packages/react-native-web/src/exports/Button/__tests__/index-test.js index 01e4ce875a..8b3e223a50 100644 --- a/packages/react-native-web/src/exports/Button/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Button/__tests__/index-test.js @@ -1,8 +1,7 @@ import Button from '..'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/Button', () => { test('prop "accessibilityLabel"', () => { diff --git a/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js b/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js index abe55a8b01..a1ee252c8f 100644 --- a/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/CheckBox/__tests__/index-test.js @@ -7,9 +7,8 @@ import CheckBox from '../'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; function findCheckbox(container) { return container.firstChild.querySelector('input'); diff --git a/packages/react-native-web/src/exports/Image/__tests__/index-test.js b/packages/react-native-web/src/exports/Image/__tests__/index-test.js index 3be0434e2e..e335fa0475 100644 --- a/packages/react-native-web/src/exports/Image/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Image/__tests__/index-test.js @@ -7,13 +7,12 @@ /* eslint-disable react/jsx-no-bind */ -import { act } from 'react-dom/test-utils'; import * as AssetRegistry from '../../../modules/AssetRegistry'; import Image from '../'; import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader'; import PixelRatio from '../../PixelRatio'; import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; const originalImage = window.Image; diff --git a/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js b/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js index f9371f30c8..87fc3aa1f9 100644 --- a/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js @@ -7,9 +7,8 @@ import React from 'react'; import Pressable from '../'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/Pressable', () => { test('default', () => { diff --git a/packages/react-native-web/src/exports/ScrollView/__tests__/index-test.js b/packages/react-native-web/src/exports/ScrollView/__tests__/index-test.js index 9c8f2d1ab2..68e4174df5 100644 --- a/packages/react-native-web/src/exports/ScrollView/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/ScrollView/__tests__/index-test.js @@ -1,9 +1,8 @@ import React from 'react'; import ScrollView from '../'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; import { findDOMNode } from 'react-dom'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/ScrollView', () => { describe('prop "centerContent"', () => { diff --git a/packages/react-native-web/src/exports/Text/__tests__/index-test.js b/packages/react-native-web/src/exports/Text/__tests__/index-test.js index 43157b5aa4..6d893d6913 100644 --- a/packages/react-native-web/src/exports/Text/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Text/__tests__/index-test.js @@ -9,9 +9,8 @@ import React from 'react'; import Text from '../'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/Text', () => { test('default', () => { diff --git a/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js b/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js index f6b1923b54..8baa9c6be3 100644 --- a/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/TextInput/__tests__/index-test.js @@ -7,9 +7,8 @@ import React from 'react'; import TextInput from '..'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; function findInput(container) { return container.querySelector('input'); diff --git a/packages/react-native-web/src/exports/Touchable/index.js b/packages/react-native-web/src/exports/Touchable/index.js index 684dfa196d..c521b2291a 100644 --- a/packages/react-native-web/src/exports/Touchable/index.js +++ b/packages/react-native-web/src/exports/Touchable/index.js @@ -13,7 +13,6 @@ import type { Node } from 'React'; import AccessibilityUtil from '../../modules/AccessibilityUtil'; import BoundingDimensions from './BoundingDimensions'; -import findNodeHandle from '../findNodeHandle'; import normalizeColor from 'normalize-css-color'; import Position from './Position'; import React from 'react'; @@ -73,6 +72,11 @@ const extractSingleTouch = (nativeEvent) => { * return merge(this.touchableGetInitialState(), yourComponentState); * } * + * - Add a method to get your touchable component's node. + * getTouchableNode: function() { + * return this.touchableRef.current + * } + * * - Choose the rendered component who's touches should start the interactive * sequence. On that rendered node, forward all `Touchable` responder * handlers. You can choose any rendered node you like. Choose a node whose @@ -81,6 +85,7 @@ const extractSingleTouch = (nativeEvent) => { * // In render function: * return ( * { if (this._isTouchableKeyboardActive) { if ( @@ -379,7 +384,7 @@ const TouchableMixin = { this._isTouchableKeyboardActive = false; } }; - this._touchableNode.addEventListener('blur', this._touchableBlurListener); + touchableNode.addEventListener('blur', this._touchableBlurListener); } }, @@ -387,11 +392,9 @@ const TouchableMixin = { * Clear all timeouts on unmount */ componentWillUnmount: function () { - if (this._touchableNode && this._touchableNode.addEventListener) { - this._touchableNode.removeEventListener( - 'blur', - this._touchableBlurListener - ); + const touchableNode = this.getTouchableNode && this.getTouchableNode(); + if (touchableNode && touchableNode.addEventListener) { + touchableNode.removeEventListener('blur', this._touchableBlurListener); } this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout); this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout); @@ -399,7 +402,6 @@ const TouchableMixin = { // Clear DOM nodes this.pressInLocation = null; this.state.touchable.responderID = null; - this._touchableNode = null; }, /** diff --git a/packages/react-native-web/src/exports/View/__tests__/index-test.js b/packages/react-native-web/src/exports/View/__tests__/index-test.js index 58a873c926..ee35f8abda 100644 --- a/packages/react-native-web/src/exports/View/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/View/__tests__/index-test.js @@ -8,9 +8,8 @@ import React from 'react'; import View from '../'; import StyleSheet from '../../StyleSheet'; -import { act } from 'react-dom/test-utils'; import { createEventTarget } from 'dom-event-testing-library'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; describe('components/View', () => { test('default', () => { diff --git a/packages/react-native-web/src/exports/findNodeHandle/index.js b/packages/react-native-web/src/exports/findNodeHandle/index.js index 08d91ae560..f0b6ddfae5 100644 --- a/packages/react-native-web/src/exports/findNodeHandle/index.js +++ b/packages/react-native-web/src/exports/findNodeHandle/index.js @@ -10,6 +10,10 @@ import { findDOMNode } from 'react-dom'; +/** + * @deprecated imperatively finding the DOM element of a react component has been deprecated in React 18. + * You should use ref properties on the component instead. + */ const findNodeHandle = (component) => { let node; diff --git a/packages/react-native-web/src/exports/render/index.js b/packages/react-native-web/src/exports/render/index.js index a33ae508fe..8002132585 100644 --- a/packages/react-native-web/src/exports/render/index.js +++ b/packages/react-native-web/src/exports/render/index.js @@ -7,15 +7,46 @@ * @noflow */ -import { hydrate as domHydrate, render as domRender } from 'react-dom'; +import { + hydrate as domLegacyHydrate, + render as domLegacyRender +} from 'react-dom'; +import { + createRoot as domCreateRoot, + hydrateRoot as domHydrateRoot +} from 'react-dom/client'; + +import unmountComponentAtNode from '../unmountComponentAtNode'; import { createSheet } from '../StyleSheet/dom'; -export function hydrate(element, root, callback) { +export function hydrate(element, root) { + createSheet(root); + return domHydrateRoot(root, element); +} + +export function render(element, root) { + createSheet(root); + const reactRoot = domCreateRoot(root); + reactRoot.render(element); + return reactRoot; +} + +export function hydrateLegacy(element, root, callback) { createSheet(root); - return domHydrate(element, root, callback); + domLegacyHydrate(element, root, callback); + return { + unmount: function () { + return unmountComponentAtNode(root); + } + }; } -export default function render(element, root, callback) { +export default function renderLegacy(element, root, callback) { createSheet(root); - return domRender(element, root, callback); + domLegacyRender(element, root, callback); + return { + unmount: function () { + return unmountComponentAtNode(root); + } + }; } diff --git a/packages/react-native-web/src/modules/ScrollResponder/index.js b/packages/react-native-web/src/modules/ScrollResponder/index.js index 9281fd45a2..5d0e8d34f9 100644 --- a/packages/react-native-web/src/modules/ScrollResponder/index.js +++ b/packages/react-native-web/src/modules/ScrollResponder/index.js @@ -9,7 +9,6 @@ */ import Dimensions from '../../exports/Dimensions'; -import findNodeHandle from '../../exports/findNodeHandle'; import invariant from 'fbjs/lib/invariant'; import Platform from '../../exports/Platform'; import TextInputState from '../TextInputState'; @@ -347,17 +346,6 @@ const ScrollResponderMixin = { return isAnimating; }, - /** - * Returns the node that represents native view that can be scrolled. - * Components can pass what node to use by defining a `getScrollableNode` - * function otherwise `this` is used. - */ - scrollResponderGetScrollableNode: function (): any { - return this.getScrollableNode - ? this.getScrollableNode() - : findNodeHandle(this); - }, - /** * A helper function to scroll to a specific point in the scrollview. * This is currently used to help focus on child textviews, but can also @@ -381,7 +369,7 @@ const ScrollResponderMixin = { } else { ({ x, y, animated } = x || emptyObject); } - const node = this.scrollResponderGetScrollableNode(); + const node = this.getScrollableNode(); const left = x || 0; const top = y || 0; if (typeof node.scroll === 'function') { @@ -437,7 +425,7 @@ const ScrollResponderMixin = { this.preventNegativeScrollOffset = !!preventNegativeScrollOffset; UIManager.measureLayout( nodeHandle, - findNodeHandle(this.getInnerViewNode()), + this.getInnerViewNode(), this.scrollResponderTextInputFocusError, this.scrollResponderInputMeasureAndScrollToKeyboard ); diff --git a/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.js b/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.js index 2440493b5a..f62e22ef95 100644 --- a/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.js @@ -8,53 +8,11 @@ */ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import * as ReactDOMServer from 'react-dom/server'; -import { act } from 'react-dom/test-utils'; +import { act, render } from '@testing-library/react'; import { createEventTarget } from 'dom-event-testing-library'; import createEventHandle from '..'; -function createRoot(rootNode) { - return { - render(element) { - ReactDOM.render(element, rootNode); - } - }; -} - describe('create-event-handle', () => { - let root; - let rootNode; - - beforeEach(() => { - rootNode = document.createElement('div'); - document.body.appendChild(rootNode); - root = createRoot(rootNode); - }); - - afterEach(() => { - root.render(null); - document.body.removeChild(rootNode); - rootNode = null; - root = null; - }); - - test('can render correctly using ReactDOMServer', () => { - const listener = jest.fn(); - const targetRef = React.createRef(); - const addClickListener = createEventHandle('click'); - - function Component() { - React.useEffect(() => { - return addClickListener(targetRef.current, listener); - }); - return
; - } - - const output = ReactDOMServer.renderToString(); - expect(output).toBe('
'); - }); - describe('createEventTarget()', () => { test('event dispatched on target', () => { const listener = jest.fn(); @@ -68,9 +26,7 @@ describe('create-event-handle', () => { return
; } - act(() => { - root.render(); - }); + render(); const target = createEventTarget(targetRef.current); @@ -113,9 +69,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const parent = createEventTarget(parentRef.current); @@ -164,9 +118,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -196,9 +148,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const text = createEventTarget(childRef.current.firstChild); @@ -221,9 +171,8 @@ describe('create-event-handle', () => { return
; } - act(() => { - root.render(); - }); + render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); @@ -244,9 +193,8 @@ describe('create-event-handle', () => { return
; } - act(() => { - root.render(); - }); + render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); @@ -267,9 +215,7 @@ describe('create-event-handle', () => { return
; } - act(() => { - root.render(); - }); + render(); act(() => { const event = new CustomEvent('magic-event', { bubbles: true }); @@ -312,9 +258,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -371,9 +315,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -416,9 +358,7 @@ describe('create-event-handle', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -447,9 +387,7 @@ describe('create-event-handle', () => { return
; } - act(() => { - root.render(); - }); + render(); const target = createEventTarget(targetRef.current); diff --git a/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.node.js b/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.node.js new file mode 100644 index 0000000000..b9c7e0e6ef --- /dev/null +++ b/packages/react-native-web/src/modules/createEventHandle/__tests__/index-test.node.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import * as React from 'react'; +import * as ReactDOMServer from 'react-dom/server'; +import createEventHandle from '..'; + +describe('create-event-handle', () => { + test('can render correctly using ReactDOMServer', () => { + const listener = jest.fn(); + const targetRef = React.createRef(); + const addClickListener = createEventHandle('click'); + + function Component() { + React.useEffect(() => { + return addClickListener(targetRef.current, listener); + }); + return
; + } + + const output = ReactDOMServer.renderToString(); + expect(output).toBe('
'); + }); +}); diff --git a/packages/react-native-web/src/modules/useEvent/__tests__/index-test.js b/packages/react-native-web/src/modules/useEvent/__tests__/index-test.js index 2985695be1..61bb548d99 100644 --- a/packages/react-native-web/src/modules/useEvent/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/useEvent/__tests__/index-test.js @@ -8,36 +8,11 @@ */ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { act } from 'react-dom/test-utils'; +import { act, render } from '@testing-library/react'; import { createEventTarget } from 'dom-event-testing-library'; import useEvent from '..'; -function createRoot(rootNode) { - return { - render(element) { - ReactDOM.render(element, rootNode); - } - }; -} - describe('use-event', () => { - let root; - let rootNode; - - beforeEach(() => { - rootNode = document.createElement('div'); - document.body.appendChild(rootNode); - root = createRoot(rootNode); - }); - - afterEach(() => { - root.render(null); - document.body.removeChild(rootNode); - rootNode = null; - root = null; - }); - describe('setListener()', () => { test('event dispatched on target', () => { const listener = jest.fn(); @@ -51,9 +26,7 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + render(); const target = createEventTarget(targetRef.current); @@ -85,9 +58,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const parent = createEventTarget(parentRef.current); @@ -125,9 +96,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -157,9 +126,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const text = createEventTarget(childRef.current.firstChild); @@ -182,9 +149,8 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); @@ -205,9 +171,8 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); @@ -229,18 +194,16 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + const { rerender } = render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); }); expect(listener).toBeCalledTimes(1); - act(() => { - // this should replace the listener - root.render(); - }); + + rerender(); + act(() => { target.click(); }); @@ -260,18 +223,17 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + const { unmount } = render(); + const target = createEventTarget(targetRef.current); act(() => { target.click(); }); expect(listener).toBeCalledTimes(1); - act(() => { - // this should unset the listener - root.render(); - }); + + // this should unset the listener + unmount(); + listener.mockClear(); act(() => { target.click(); @@ -291,9 +253,7 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + render(); act(() => { const event = new CustomEvent('magic-event', { bubbles: true }); @@ -335,9 +295,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -391,9 +349,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -425,10 +381,8 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - root.render(null); - }); + const { unmount } = render(); + unmount(); const target = createEventTarget(document); @@ -462,9 +416,7 @@ describe('use-event', () => { ); } - act(() => { - root.render(); - }); + render(); const child = createEventTarget(childRef.current); @@ -493,9 +445,7 @@ describe('use-event', () => { return
; } - act(() => { - root.render(); - }); + render(); const target = createEventTarget(targetRef.current); diff --git a/packages/react-native-web/src/modules/useHover/__tests__/index-test.js b/packages/react-native-web/src/modules/useHover/__tests__/index-test.js index a9e0d18fc5..a5a4958caa 100644 --- a/packages/react-native-web/src/modules/useHover/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/useHover/__tests__/index-test.js @@ -5,9 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import { act } from 'react-dom/test-utils'; +import { act, render } from '@testing-library/react'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; import { describeWithPointerEvent, clearPointers, @@ -17,30 +16,12 @@ import { import useHover from '..'; import { testOnly_resetActiveModality } from '../../modality'; -function createRoot(rootNode) { - return { - render(element) { - ReactDOM.render(element, rootNode); - } - }; -} - describeWithPointerEvent('useHover', (hasPointerEvents) => { - let root; - let rootNode; - beforeEach(() => { setPointerEvent(hasPointerEvents); - rootNode = document.createElement('div'); - document.body.appendChild(rootNode); - root = createRoot(rootNode); }); afterEach(() => { - root.render(null); - document.body.removeChild(rootNode); - rootNode = null; - root = null; testOnly_resetActiveModality(); // make sure all tests reset state machine tracking pointers on the mock surface clearPointers(); @@ -70,9 +51,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => {
); }; - act(() => { - root.render(); - }); + render(); }; test('contains the hover gesture', () => { @@ -111,9 +90,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => { }); return
; }; - act(() => { - root.render(); - }); + render(); }; test('does not call callbacks', () => { @@ -140,9 +117,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => { useHover(ref, { onHoverStart }); return
; }; - act(() => { - root.render(); - }); + render(); }; test('is called for mouse pointers', () => { @@ -186,9 +161,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => { useHover(ref, { onHoverChange }); return
; }; - act(() => { - root.render(); - }); + render(); }; test('is called for mouse pointers', () => { @@ -232,9 +205,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => {
); }; - act(() => { - root.render(); - }); + render(); }; test('is called for mouse pointers', () => { @@ -278,9 +249,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => { useHover(ref, { onHoverUpdate }); return
; }; - act(() => { - root.render(); - }); + render(); const target = createEventTarget(ref.current); act(() => { @@ -310,9 +279,7 @@ describeWithPointerEvent('useHover', (hasPointerEvents) => { }); return
; }; - act(() => { - root.render(); - }); + render(); }; test('callbacks are called each time', () => { diff --git a/packages/react-native-web/src/modules/useMergeRefs/__tests__/index-test.js b/packages/react-native-web/src/modules/useMergeRefs/__tests__/index-test.js index 3116695bc1..a1a246156d 100644 --- a/packages/react-native-web/src/modules/useMergeRefs/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/useMergeRefs/__tests__/index-test.js @@ -6,8 +6,7 @@ */ import * as React from 'react'; -import { act } from 'react-dom/test-utils'; -import { cleanup, render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import useMergeRefs from '..'; describe('modules/useMergeRefs', () => { @@ -16,8 +15,6 @@ describe('modules/useMergeRefs', () => { return
; } - afterEach(cleanup); - test('handles no refs', () => { act(() => { render(); diff --git a/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js b/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js index 11324ea5a0..3a195f067c 100644 --- a/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js @@ -5,9 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import { act } from 'react-dom/test-utils'; +import { act, render } from '@testing-library/react'; import React, { createRef } from 'react'; -import ReactDOM from 'react-dom'; import useResponderEvents from '..'; import { getResponderNode, terminateResponder } from '../ResponderSystem'; import { @@ -19,21 +18,7 @@ import { } from 'dom-event-testing-library'; describe('useResponderEvents', () => { - let container; - - function render(element) { - ReactDOM.render(element, container); - } - - beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - afterEach(() => { - render(null); - document.body.removeChild(container); - container = null; // make sure all tests end with the current responder being reset terminateResponder(); // make sure all tests reset state machine tracking pointers on the mock surface @@ -52,9 +37,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture act(() => { @@ -76,9 +59,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const buttons = [1, 2, 3, 4]; // gesture @@ -105,9 +86,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const acceptedModifierKeys = ['metaKey', 'shiftKey']; const ignoredModifierKeys = ['altKey', 'ctrlKey']; @@ -139,9 +118,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // touch gesture act(() => { @@ -186,9 +163,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture act(() => { @@ -245,9 +220,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -302,9 +275,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -355,9 +326,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -426,9 +395,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -475,9 +442,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -526,9 +491,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -596,9 +559,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -653,9 +614,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -707,9 +666,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -779,9 +736,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -830,9 +785,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -882,9 +835,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -943,9 +894,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -987,9 +936,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -1038,9 +985,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -1067,9 +1012,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -1134,9 +1077,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start act(() => { @@ -1181,9 +1122,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & move act(() => { @@ -1228,9 +1167,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & end act(() => { @@ -1273,9 +1210,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture act(() => { @@ -1320,9 +1255,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start & cancel act(() => { @@ -1359,9 +1292,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const input = createEventTarget(inputRef.current); // getSelection is not supported in jest @@ -1389,9 +1320,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const doc = createEventTarget(document); // getSelection is not supported in jest @@ -1437,9 +1366,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const parent = createEventTarget(parentRef.current); // gesture start & scroll @@ -1473,9 +1400,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const doc = createEventTarget(document); // gesture start & scroll @@ -1510,9 +1435,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const sibling = createEventTarget(siblingRef.current); // gesture start & scroll @@ -1541,9 +1464,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const doc = createEventTarget(document); // gesture start & blur @@ -1571,9 +1492,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const win = createEventTarget(window); // gesture start & blur @@ -1607,9 +1526,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const sibling = createEventTarget(siblingRef.current); // gesture start & blur @@ -1640,9 +1557,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // contextmenu sequence includes pointerdown "start" act(() => { @@ -1675,9 +1590,7 @@ describe('useResponderEvents', () => { } // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const doc = createEventTarget(document); // contextmenu @@ -1860,9 +1773,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start @@ -2034,9 +1945,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // gesture start @@ -2199,9 +2108,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const parent = createEventTarget(parentRef.current); const target = createEventTarget(targetRef.current); @@ -2387,9 +2294,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const sibling = createEventTarget(siblingRef.current); // gesture start on target @@ -2530,9 +2435,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const sibling = createEventTarget(siblingRef.current); // gesture start and move on target @@ -2636,9 +2539,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); // first touch act(() => { @@ -2773,9 +2674,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); const parent = createEventTarget(parentRef.current); // first touch @@ -2876,9 +2775,7 @@ describe('useResponderEvents', () => { }; // render - act(() => { - render(); - }); + render(); const target = createEventTarget(targetRef.current); act(() => { target.pointerdown({ pointerType }); diff --git a/packages/react-native-web/src/modules/useStable/__tests__/index-test.js b/packages/react-native-web/src/modules/useStable/__tests__/index-test.js index 24f28da92d..fd42f553d7 100644 --- a/packages/react-native-web/src/modules/useStable/__tests__/index-test.js +++ b/packages/react-native-web/src/modules/useStable/__tests__/index-test.js @@ -8,21 +8,10 @@ */ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { act } from 'react-dom/test-utils'; +import { render } from '@testing-library/react'; import useStable from '..'; -function createRoot(rootNode) { - return { - render(element) { - ReactDOM.render(element, rootNode); - } - }; -} - describe('useStable', () => { - let root; - let rootNode; let spy = {}; const TestComponent = ({ initialValueCallback }): React.Node => { @@ -33,59 +22,33 @@ describe('useStable', () => { beforeEach(() => { spy = {}; - rootNode = document.createElement('div'); - document.body.appendChild(rootNode); - root = createRoot(rootNode); - }); - - afterEach(() => { - root.render(null); - document.body.removeChild(rootNode); - rootNode = null; - root = null; }); test('correctly sets the initial value', () => { const initialValueCallback = () => 5; - act(() => { - root.render( - - ); - }); + render(); expect(spy.value).toBe(5); }); test('does not change the value', () => { let counter = 0; const initialValueCallback = () => counter++; - act(() => { - root.render( - - ); - }); + const { rerender } = render( + + ); expect(spy.value).toBe(0); - act(() => { - root.render( - - ); - }); + rerender(); expect(spy.value).toBe(0); }); test('only calls the callback once', () => { let counter = 0; const initialValueCallback = () => counter++; - act(() => { - root.render( - - ); - }); + const { rerender } = render( + + ); expect(counter).toBe(1); - act(() => { - root.render( - - ); - }); + rerender(); expect(counter).toBe(1); }); @@ -98,17 +61,11 @@ describe('useStable', () => { } return counter++; }; - act(() => { - root.render( - - ); - }); + const { rerender } = render( + + ); expect(spy.value).toBe(null); - act(() => { - root.render( - - ); - }); + rerender(); expect(spy.value).toBe(null); }); }); diff --git a/packages/react-native-web/src/vendor/react-native/Animated/AnimatedEvent.js b/packages/react-native-web/src/vendor/react-native/Animated/AnimatedEvent.js index 2571a963af..3d673ab6b8 100644 --- a/packages/react-native-web/src/vendor/react-native/Animated/AnimatedEvent.js +++ b/packages/react-native-web/src/vendor/react-native/Animated/AnimatedEvent.js @@ -12,7 +12,6 @@ import AnimatedValue from './nodes/AnimatedValue'; import NativeAnimatedHelper from './NativeAnimatedHelper'; -import findNodeHandle from '../../../exports/findNodeHandle'; import invariant from 'fbjs/lib/invariant'; @@ -58,11 +57,10 @@ export function attachNativeEvent( // Assume that the event containing `nativeEvent` is always the first argument. traverse(argMapping[0].nativeEvent, []); - const viewTag = findNodeHandle(viewRef); - if (viewTag != null) { + if (viewRef != null) { eventMappings.forEach(mapping => { NativeAnimatedHelper.API.addAnimatedEventToView( - viewTag, + viewRef, eventName, mapping, ); @@ -71,10 +69,10 @@ export function attachNativeEvent( return { detach() { - if (viewTag != null) { + if (viewRef != null) { eventMappings.forEach(mapping => { NativeAnimatedHelper.API.removeAnimatedEventFromView( - viewTag, + viewRef, eventName, // $FlowFixMe[incompatible-call] mapping.animatedValueTag, diff --git a/packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedProps.js b/packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedProps.js index 2189d3f56b..65922d06ad 100644 --- a/packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedProps.js +++ b/packages/react-native-web/src/vendor/react-native/Animated/nodes/AnimatedProps.js @@ -14,7 +14,6 @@ import {AnimatedEvent} from '../AnimatedEvent'; import AnimatedNode from './AnimatedNode'; import AnimatedStyle from './AnimatedStyle'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; -import findNodeHandle from '../../../../exports/findNodeHandle'; import invariant from 'fbjs/lib/invariant'; @@ -119,9 +118,7 @@ class AnimatedProps extends AnimatedNode { __connectAnimatedView(): void { invariant(this.__isNative, 'Expected node to be marked as "native"'); - const nativeViewTag: ?number = findNodeHandle( - this._animatedView, - ); + const nativeViewTag: ?number = this._animatedView invariant( nativeViewTag != null, 'Unable to locate attached view in the native tree', @@ -134,9 +131,7 @@ class AnimatedProps extends AnimatedNode { __disconnectAnimatedView(): void { invariant(this.__isNative, 'Expected node to be marked as "native"'); - const nativeViewTag: ?number = findNodeHandle( - this._animatedView, - ); + const nativeViewTag: ?number = this._animatedView invariant( nativeViewTag != null, 'Unable to locate attached view in the native tree', diff --git a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js index 180ec22aca..9b7be3fd8e 100644 --- a/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js +++ b/packages/react-native-web/src/vendor/react-native/VirtualizedList/index.js @@ -10,7 +10,6 @@ import Batchinator from '../Batchinator'; import FillRateHelper from '../FillRateHelper'; -import findNodeHandle from '../../../exports/findNodeHandle'; import RefreshControl from '../../../exports/RefreshControl'; import ScrollView from '../../../exports/ScrollView'; import StyleSheet from '../../../exports/StyleSheet'; @@ -573,7 +572,7 @@ class VirtualizedList extends React.PureComponent { if (this._scrollRef && this._scrollRef.getScrollableNode) { return this._scrollRef.getScrollableNode(); } else { - return findNodeHandle(this._scrollRef); + return this._scrollRef; } }