Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use render after hydrate #19442

Merged
merged 1 commit into from Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/next/client/index.tsx
Expand Up @@ -477,6 +477,7 @@ export function renderError(renderErrorProps: RenderErrorProps) {
}

let reactRoot: any = null
let shouldUseHydrate = typeof ReactDOM.hydrate === 'function'
function renderReactElement(reactEl: JSX.Element, domEl: HTMLElement) {
if (process.env.__NEXT_REACT_MODE !== 'legacy') {
if (!reactRoot) {
Expand All @@ -494,8 +495,9 @@ function renderReactElement(reactEl: JSX.Element, domEl: HTMLElement) {
}

// The check for `.hydrate` is there to support React alternatives like preact
if (typeof ReactDOM.hydrate === 'function') {
if (shouldUseHydrate) {
ReactDOM.hydrate(reactEl, domEl, markHydrateComplete)
shouldUseHydrate = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving shouldUseHydrate = false before the call to hydrate()?

} else {
ReactDOM.render(reactEl, domEl, markRenderComplete)
}
Expand Down
25 changes: 25 additions & 0 deletions test/integration/hydrate-then-render/pages/_app.js
@@ -0,0 +1,25 @@
/* eslint-disable camelcase */
import App from 'next/app'

if (typeof navigator !== 'undefined') {
window.__BEACONS = window.__BEACONS || []

navigator.sendBeacon = async function () {
const args = await Promise.all(
[...arguments].map((v) => {
if (v instanceof Blob) {
return v.text()
}
return v
})
)

window.__BEACONS.push(args)
}
}

export default class MyApp extends App {}

export function reportWebVitals(data) {
navigator.sendBeacon('/test', new URLSearchParams(data).toString())
}
15 changes: 15 additions & 0 deletions test/integration/hydrate-then-render/pages/index.js
@@ -0,0 +1,15 @@
import Link from 'next/link'

const Home = () => {
return (
<div>
<h1>Foo!</h1>
<h2>bar!</h2>
<Link href="/other">
<a id="to-other">Other</a>
</Link>
</div>
)
}

export default Home
11 changes: 11 additions & 0 deletions test/integration/hydrate-then-render/pages/other.js
@@ -0,0 +1,11 @@
const Other = () => {
return (
<div>
<h1>Foo!</h1>
<h2>bar!</h2>
<div id="on-other"></div>
</div>
)
}

export default Other
37 changes: 37 additions & 0 deletions test/integration/hydrate-then-render/test/index.test.js
@@ -0,0 +1,37 @@
/* eslint-env jest */

import { findPort, killApp, nextBuild, nextStart } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'

const appDir = join(__dirname, '../')
let appPort
let server
jest.setTimeout(1000 * 60 * 2)

describe('hydrate/render ordering', () => {
beforeAll(async () => {
appPort = await findPort()
await nextBuild(appDir, [], {})
server = await nextStart(appDir, appPort)
})
afterAll(() => killApp(server))

it('correctly measures hydrate followed by render', async () => {
const browser = await webdriver(appPort, '/')
await browser.waitForElementByCss('#to-other')
await browser.elementByCss('#to-other').click()
await browser.waitForElementByCss('#on-other')

const beacons = (await browser.eval('window.__BEACONS'))
.map(([, value]) => Object.fromEntries(new URLSearchParams(value)))
.filter((p) => p.label === 'custom')
expect(beacons).toMatchObject([
{ name: 'Next.js-hydration' },
{ name: 'Next.js-render' },
{ name: 'Next.js-route-change-to-render' },
])

await browser.close()
})
})