Skip to content

Commit 399e510

Browse files
arunodarauchg
authored andcommitted
Make sure lastAppProps always have some value. (vercel#829)
* Make sure lastAppProps always have some value. * Revert "Make sure lastAppProps always have some value." This reverts commit b4ae722. * Throw an error, if we found an empty object from getInitialProps. * Add proper tests for getInitialProps empty check.
1 parent 318f110 commit 399e510

File tree

11 files changed

+62
-19
lines changed

11 files changed

+62
-19
lines changed

client/index.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { rehydrate } from '../lib/css'
55
import { createRouter } from '../lib/router'
66
import App from '../lib/app'
77
import evalScript from '../lib/eval-script'
8+
import { loadGetInitialProps } from '../lib/utils'
89

910
const {
1011
__NEXT_DATA__: {
@@ -51,7 +52,7 @@ export async function render (props, onError = renderErrorComponent) {
5152

5253
async function renderErrorComponent (err) {
5354
const { pathname, query } = router
54-
const props = await getInitialProps(ErrorComponent, { err, pathname, query })
55+
const props = await loadGetInitialProps(ErrorComponent, { err, pathname, query })
5556
await doRender({ Component: ErrorComponent, props, err })
5657
}
5758

@@ -61,7 +62,7 @@ async function doRender ({ Component, props, err }) {
6162
lastAppProps.Component === ErrorComponent) {
6263
// fetch props if ErrorComponent was replaced with a page component by HMR
6364
const { pathname, query } = router
64-
props = await getInitialProps(Component, { err, pathname, query })
65+
props = await loadGetInitialProps(Component, { err, pathname, query })
6566
}
6667

6768
Component = Component || lastAppProps.Component
@@ -71,7 +72,3 @@ async function doRender ({ Component, props, err }) {
7172
lastAppProps = appProps
7273
ReactDOM.render(createElement(App, appProps), container)
7374
}
74-
75-
function getInitialProps (Component, ctx) {
76-
return Component.getInitialProps ? Component.getInitialProps(ctx) : {}
77-
}

examples/with-loading/pages/about.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import Header from '../components/Header'
33

44
export default class About extends Component {
55
// Add some delay
6-
static getInitialProps () {
7-
return new Promise((resolve) => {
6+
static async getInitialProps () {
7+
await new Promise((resolve) => {
88
setTimeout(resolve, 500)
99
})
10+
return {}
1011
}
1112

1213
render () {

examples/with-loading/pages/forever.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import Header from '../components/Header'
33

44
export default class Forever extends Component {
55
// Add some delay
6-
static getInitialProps () {
7-
return new Promise((resolve) => {
6+
static async getInitialProps () {
7+
await new Promise((resolve) => {
88
setTimeout(resolve, 3000)
99
})
10+
return {}
1011
}
1112

1213
render () {

lib/router/router.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import evalScript from '../eval-script'
55
import shallowEquals from '../shallow-equals'
66
import { EventEmitter } from 'events'
77
import { reloadIfPrefetched } from '../prefetch'
8+
import { loadGetInitialProps } from '../utils'
89

910
export default class Router extends EventEmitter {
1011
constructor (pathname, query, { Component, ErrorComponent, err } = {}) {
@@ -234,7 +235,7 @@ export default class Router extends EventEmitter {
234235
const cancel = () => { cancelled = true }
235236
this.componentLoadCancel = cancel
236237

237-
const props = await (Component.getInitialProps ? Component.getInitialProps(ctx) : {})
238+
const props = await loadGetInitialProps(Component, ctx)
238239

239240
if (cancel === this.componentLoadCancel) {
240241
this.componentLoadCancel = null

lib/utils.js

+12
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,15 @@ export function printAndExit (message, code = 1) {
4141

4242
process.exit(code)
4343
}
44+
45+
export async function loadGetInitialProps (Component, ctx) {
46+
if (!Component.getInitialProps) return {}
47+
48+
const props = await Component.getInitialProps(ctx)
49+
if (!props) {
50+
const compName = Component.displayName || Component.name
51+
const message = `"${compName}.getInitialProps()" should resolve to an object. But found "${props}" instead.`
52+
throw new Error(message)
53+
}
54+
return props
55+
}

server/render.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import requireModule from './require'
99
import resolvePath from './resolve'
1010
import readPage from './read-page'
1111
import { Router } from '../lib/router'
12+
import { loadGetInitialProps } from '../lib/utils'
1213
import Head, { defaultHead } from '../lib/head'
1314
import App from '../lib/app'
1415

@@ -52,7 +53,7 @@ async function doRender (req, res, pathname, query, {
5253
component,
5354
errorComponent
5455
] = await Promise.all([
55-
Component.getInitialProps ? Component.getInitialProps(ctx) : {},
56+
loadGetInitialProps(Component, ctx),
5657
readPage(join(dir, '.next', 'bundles', 'pages', page)),
5758
readPage(join(dir, '.next', 'bundles', 'pages', '_error'))
5859
])
@@ -80,7 +81,7 @@ async function doRender (req, res, pathname, query, {
8081
return { html, head }
8182
}
8283

83-
const docProps = await Document.getInitialProps({ ...ctx, renderPage })
84+
const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage })
8485

8586
const doc = createElement(Document, {
8687
__NEXT_DATA__: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const EmptyInitialPropsPage = () => (<div>My Page</div>)
2+
EmptyInitialPropsPage.getInitialProps = () => null
3+
4+
export default EmptyInitialPropsPage

test/integration/basic/pages/nav/about.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Link from 'next/link'
33
export default () => (
44
<div className='nav-about'>
55
<Link href='/nav'>
6-
<a>Go Back</a>
6+
<a id='home-link'>Go Back</a>
77
</Link>
88

99
<p>This is the about page.</p>

test/integration/basic/pages/nav/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { Component } from 'react'
33

44
let counter = 0
55

6+
const linkStyle = {
7+
marginRight: 10
8+
}
9+
610
export default class extends Component {
711

812
increase () {
@@ -13,7 +17,8 @@ export default class extends Component {
1317
render () {
1418
return (
1519
<div className='nav-home'>
16-
<Link href='/nav/about'><a>About</a></Link>
20+
<Link href='/nav/about'><a id='about-link' style={linkStyle}>About</a></Link>
21+
<Link href='/empty-get-initial-props'><a id='empty-props' style={linkStyle}>Empty Props</a></Link>
1722
<p>This is the home.</p>
1823
<div id='counter'>
1924
Counter: {counter}

test/integration/basic/test/client-navigation.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default (context) => {
88
it('should navigate the page', async () => {
99
const browser = await webdriver(context.appPort, '/nav')
1010
const text = await browser
11-
.elementByCss('a').click()
11+
.elementByCss('#about-link').click()
1212
.waitForElementByCss('.nav-about')
1313
.elementByCss('p').text()
1414

@@ -21,9 +21,9 @@ export default (context) => {
2121

2222
const counterText = await browser
2323
.elementByCss('#increase').click()
24-
.elementByCss('a').click()
24+
.elementByCss('#about-link').click()
2525
.waitForElementByCss('.nav-about')
26-
.elementByCss('a').click()
26+
.elementByCss('#home-link').click()
2727
.waitForElementByCss('.nav-home')
2828
.elementByCss('#counter').text()
2929

@@ -36,13 +36,28 @@ export default (context) => {
3636
it('should navigate the page', async () => {
3737
const browser = await webdriver(context.appPort, '/nav/about')
3838
const text = await browser
39-
.elementByCss('a').click()
39+
.elementByCss('#home-link').click()
4040
.waitForElementByCss('.nav-home')
4141
.elementByCss('p').text()
4242

4343
expect(text).toBe('This is the home.')
4444
await browser.close()
4545
})
4646
})
47+
48+
describe('with empty getInitialProps()', () => {
49+
it('should render an error', async () => {
50+
const browser = await webdriver(context.appPort, '/nav')
51+
const preText = await browser
52+
.elementByCss('#empty-props').click()
53+
.waitForElementByCss('pre')
54+
.elementByCss('pre').text()
55+
56+
const expectedErrorMessage = '"EmptyInitialPropsPage.getInitialProps()" should resolve to an object. But found "null" instead.'
57+
expect(preText.includes(expectedErrorMessage)).toBeTruthy()
58+
59+
await browser.close()
60+
})
61+
})
4762
})
4863
}

test/integration/basic/test/rendering.js

+6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ export default function ({ app }, suiteName, render) {
5555
expect(link.text()).toBe('About')
5656
})
5757

58+
test('getInitialProps resolves to null', async () => {
59+
const $ = await get$('/empty-get-initial-props')
60+
const expectedErrorMessage = '"EmptyInitialPropsPage.getInitialProps()" should resolve to an object. But found "null" instead.'
61+
expect($('pre').text().includes(expectedErrorMessage)).toBeTruthy()
62+
})
63+
5864
test('error', async () => {
5965
const $ = await get$('/error')
6066
expect($('pre').text()).toMatch(/This is an expected error/)

0 commit comments

Comments
 (0)