diff --git a/package.json b/package.json index c31b45d07d002..f0fa1bf9c2e7e 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,9 @@ "pretty-bytes": "5.3.0", "pretty-ms": "7.0.0", "react": "17.0.2", + "react-18": "npm:react@next", "react-dom": "17.0.2", + "react-dom-18": "npm:react-dom@next", "react-ssr-prepass": "1.0.8", "release": "6.3.0", "request-promise-core": "1.1.2", diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 7f78aa68885f5..5ed91e18775fe 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -489,7 +489,8 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise { } let reactRoot: any = null -let shouldHydrate: boolean = typeof ReactDOM.hydrate === 'function' +// On initial render a hydrate should always happen +let shouldHydrate: boolean = true function renderReactElement( domEl: HTMLElement, @@ -503,12 +504,13 @@ function renderReactElement( const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete) if (process.env.__NEXT_REACT_ROOT) { if (!reactRoot) { - reactRoot = (ReactDOM as any).createRoot(domEl, { - hydrate: shouldHydrate, - }) + // Unlike with createRoot, you don't need a separate root.render() call here + reactRoot = (ReactDOM as any).hydrateRoot(domEl, reactEl) + // TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing + shouldHydrate = false + } else { + reactRoot.render(reactEl) } - reactRoot.render(reactEl) - shouldHydrate = false } else { // The check for `.hydrate` is there to support React alternatives like preact if (shouldHydrate) { diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js index ef085280f3d45..233a760a02387 100644 --- a/test/integration/build-output/test/index.test.js +++ b/test/integration/build-output/test/index.test.js @@ -129,7 +129,7 @@ describe('Build Output', () => { expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1) expect(err404Size.endsWith('kB')).toBe(true) - expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 205, 1) + expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1) expect(err404FirstLoad.endsWith('kB')).toBe(true) expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1) diff --git a/test/integration/fallback-modules/test/index.test.js b/test/integration/fallback-modules/test/index.test.js index 57558d4eef5e3..eda446c171553 100644 --- a/test/integration/fallback-modules/test/index.test.js +++ b/test/integration/fallback-modules/test/index.test.js @@ -49,7 +49,7 @@ describe('Build Output', () => { expect(indexSize.endsWith('kB')).toBe(true) expect(parseFloat(indexFirstLoad)).toBeLessThanOrEqual( - process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68 : 67.9 + process.env.NEXT_PRIVATE_TEST_WEBPACK4_MODE ? 68.1 : 67.9 ) expect(parseFloat(indexFirstLoad)).toBeGreaterThanOrEqual(60) expect(indexFirstLoad.endsWith('kB')).toBe(true) diff --git a/test/integration/react-18/prerelease/next.config.js b/test/integration/react-18/prerelease/next.config.js new file mode 100644 index 0000000000000..21f2ba0abbec5 --- /dev/null +++ b/test/integration/react-18/prerelease/next.config.js @@ -0,0 +1,14 @@ +module.exports = { + webpack(config) { + const { alias } = config.resolve + // FIXME: resolving react/jsx-runtime https://github.com/facebook/react/issues/20235 + alias['react/jsx-dev-runtime'] = require.resolve('react/jsx-dev-runtime.js') + alias['react/jsx-runtime'] = require.resolve('react/jsx-runtime.js') + + // Use react 18 + alias['react'] = require.resolve('react-18') + alias['react-dom'] = require.resolve('react-dom-18') + + return config + }, +} diff --git a/test/integration/react-18/prerelease/node_modules/react-dom/index.js b/test/integration/react-18/prerelease/node_modules/react-dom/index.js deleted file mode 100644 index 27d7f6e85eb30..0000000000000 --- a/test/integration/react-18/prerelease/node_modules/react-dom/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { Suspense: true } diff --git a/test/integration/react-18/prerelease/node_modules/react-dom/package.json b/test/integration/react-18/prerelease/node_modules/react-dom/package.json index f89a61e597a6e..42324da484c86 100644 --- a/test/integration/react-18/prerelease/node_modules/react-dom/package.json +++ b/test/integration/react-18/prerelease/node_modules/react-dom/package.json @@ -1,4 +1,4 @@ { "name": "react-dom", - "version": "18.0.0-alpha-43f4cc160" + "version": "18.0.0-alpha-e6be2d531" } diff --git a/test/integration/react-18/prerelease/package.json b/test/integration/react-18/prerelease/package.json index 1e0b54f840c56..be823a58af7d7 100644 --- a/test/integration/react-18/prerelease/package.json +++ b/test/integration/react-18/prerelease/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "react": "*", "react-dom": "*" } } diff --git a/test/integration/react-18/prerelease/pages/index.js b/test/integration/react-18/prerelease/pages/index.js index fb077e8078c9e..17c78ff7937ea 100644 --- a/test/integration/react-18/prerelease/pages/index.js +++ b/test/integration/react-18/prerelease/pages/index.js @@ -1,3 +1,6 @@ export default function Index() { + if (typeof window !== 'undefined') { + window.didHydrate = true + } return

Hello

} diff --git a/test/integration/react-18/test/index.test.js b/test/integration/react-18/test/index.test.js index 8395643309eb1..cd866a82e528f 100644 --- a/test/integration/react-18/test/index.test.js +++ b/test/integration/react-18/test/index.test.js @@ -1,7 +1,16 @@ /* eslint-env jest */ -import { findPort, killApp, launchApp, runNextCommand } from 'next-test-utils' import { join } from 'path' +import fs from 'fs-extra' +import webdriver from 'next-webdriver' +import { + findPort, + killApp, + launchApp, + runNextCommand, + nextBuild, + nextStart, +} from 'next-test-utils' jest.setTimeout(1000 * 60 * 5) @@ -67,4 +76,21 @@ describe('React 18 Support', () => { expect(output).toMatch(UNSUPPORTED_PRERELEASE) }) }) + + describe('hydration', () => { + const appDir = join(__dirname, '../prerelease') + let app + let appPort + beforeAll(async () => { + await fs.remove(join(appDir, '.next')) + await nextBuild(appDir, [dirPrerelease]) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => await killApp(app)) + it('hydrates correctly for normal page', async () => { + const browser = await webdriver(appPort, '/') + expect(await browser.eval('window.didHydrate')).toBe(true) + }) + }) }) diff --git a/yarn.lock b/yarn.lock index f5cae140c25d8..227fdef6dae14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15632,6 +15632,23 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +"react-18@npm:react@next": + version "18.0.0-alpha-73ffce1b6-20210624" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-alpha-73ffce1b6-20210624.tgz#d9fb8700c6fad8de752ec0427f2ae3a941eea951" + integrity sha512-Qaj2vhrMlYc169Yh0gXBB7WeKWMeIVx99JnuouuT71Jku2Cly9TxAWurc+h6PSgz/qjjmDA2NOtHCb6mGlmzGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +"react-dom-18@npm:react-dom@next": + version "18.0.0-alpha-73ffce1b6-20210624" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-alpha-73ffce1b6-20210624.tgz#3d789e4f3446abc685a7754c8dc74dea0ffb4247" + integrity sha512-TgA+VhVas3mJdhy6AQLXnPzBN2JeNKC7EGhLKU11XOxUODCGQ94nyT04i1ta2R3Fv0QevMLp0Wb5hccan0wMEg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "0.21.0-alpha-73ffce1b6-20210624" + react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -16649,6 +16666,14 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +scheduler@0.21.0-alpha-73ffce1b6-20210624: + version "0.21.0-alpha-73ffce1b6-20210624" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-alpha-73ffce1b6-20210624.tgz#6fff95e26af73cfaa365b68fa3b68c4c66dfe347" + integrity sha512-7SXTiepGRo63F5Yp/fxLhZDYi5TInsqjnMTYF6GwtunUGAwyuK4V/AFiF0Q1gtB32U/e+C+OE4SSj9LtBYBjYw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"