Skip to content

Commit

Permalink
feat: Add queryReady to router. It will be true when the query va…
Browse files Browse the repository at this point in the history
…lue is populated.

fix vercel#8259
  • Loading branch information
Julian Coy committed Nov 11, 2019
1 parent 788654e commit 0ede8d2
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
9 changes: 8 additions & 1 deletion packages/next/client/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ const singletonRouter: SingletonRouterBase = {
}

// Create public properties and methods of the router in the singletonRouter
const urlPropertyFields = ['pathname', 'route', 'query', 'asPath', 'components']
const urlPropertyFields = [
'pathname',
'route',
'query',
'queryReady',
'asPath',
'components',
]
const routerEvents = [
'routeChangeStart',
'beforeHistoryChange',
Expand Down
3 changes: 3 additions & 0 deletions packages/next/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type BaseRouter = {
route: string
pathname: string
query: ParsedUrlQuery
queryReady: boolean
asPath: string
}

Expand Down Expand Up @@ -60,6 +61,7 @@ export default class Router implements BaseRouter {
route: string
pathname: string
query: ParsedUrlQuery
queryReady: boolean = false
asPath: string
/**
* Map of all components loaded in `Router`
Expand Down Expand Up @@ -119,6 +121,7 @@ export default class Router implements BaseRouter {
this.pageLoader = pageLoader
this.pathname = pathname
this.query = query
this.queryReady = true
// if auto prerendered and dynamic route wait to update asPath
// until after mount to prevent hydration mismatch
this.asPath =
Expand Down
2 changes: 2 additions & 0 deletions packages/next/next-server/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class ServerRouter implements NextRouter {
route: string
pathname: string
query: ParsedUrlQuery
queryReady: boolean
asPath: string
events: any
// TODO: Remove in the next major version, as this would mean the user is adding event listeners in server-side `render` method
Expand All @@ -60,6 +61,7 @@ class ServerRouter implements NextRouter {
this.route = pathname.replace(/\/$/, '') || '/'
this.pathname = pathname
this.query = query
this.queryReady = false
this.asPath = as
}
push(): any {
Expand Down
12 changes: 12 additions & 0 deletions test/integration/router-query-ready/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useRouter } from 'next/router'
import React from 'react'

export default () => {
const { query, queryReady } = useRouter()
return (
<>
<pre id="queryReady">{`ready: ${queryReady}`}</pre>
{queryReady ? <pre id="test">query: {query.test}</pre> : null}
</>
)
}
103 changes: 103 additions & 0 deletions test/integration/router-query-ready/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint-env jest */
/* global jasmine */
import fs from 'fs-extra'
import {
findPort,
killApp,
launchApp,
nextServer,
runNextCommand,
startApp,
stopApp,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import path, { join } from 'path'
import {
nextBuild,
nextStart,
renderViaHTTP,
} from '../../../lib/next-test-utils'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 30

let app
let appPort
let server
const appDir = join(__dirname, '../')
const nextConfig = path.join(appDir, 'next.config.js')

function runTests(params) {
describe(params ? `using query: ${params}` : 'with no query params', () => {
const pathname = params ? `/?${params}` : '/'
it('query params should not be populated on immediate sever load', async () => {
const html = await renderViaHTTP(appPort, pathname)
// this regex checks to make sure we don't have an element with id="test"
expect(html).toMatch(/^((?!id="test").)*$/i)
expect(html).toMatch(/id="queryReady">ready: false/i)
})

it('should have params after hydration on client is complete', async () => {
const browser = await webdriver(appPort, pathname)
// we don't care about parameter hydration if we didn't pass any parameters
if (params) {
// if we did pass `test=true` we want to make sure it worked
const text = await browser.elementByCss('#test').text()
expect(text).toEqual('query: true')
}
// regardless, we want to make sure router.ready was set
expect(await browser.elementByCss('#queryReady').text()).toEqual(
'ready: true'
)
})
})
}

describe('next/dynamic', () => {
describe('dev mode', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))

runTests()
runTests('test=true')
})

describe('production mode', () => {
beforeAll(async () => {
await runNextCommand(['build', appDir])

app = nextServer({
dir: appDir,
dev: false,
quiet: true,
})

server = await startApp(app)
appPort = server.address().port
})
afterAll(() => stopApp(server))

runTests()
runTests('test=true')
})

describe('serverless mode', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { target: 'serverless' }`
)
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
await fs.remove(nextConfig)
})
runTests()
runTests('test=true')
})
})

0 comments on commit 0ede8d2

Please sign in to comment.