mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Make sure lastAppProps always have some value. (#829)
* Make sure lastAppProps always have some value. * Revert "Make sure lastAppProps always have some value." This reverts commit b4ae722d9c1a4460e17dbdc041b111cbd492b2aa. * Throw an error, if we found an empty object from getInitialProps. * Add proper tests for getInitialProps empty check.
This commit is contained in:
parent
318f110a8e
commit
399e510389
|
@ -5,6 +5,7 @@ import { rehydrate } from '../lib/css'
|
||||||
import { createRouter } from '../lib/router'
|
import { createRouter } from '../lib/router'
|
||||||
import App from '../lib/app'
|
import App from '../lib/app'
|
||||||
import evalScript from '../lib/eval-script'
|
import evalScript from '../lib/eval-script'
|
||||||
|
import { loadGetInitialProps } from '../lib/utils'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
__NEXT_DATA__: {
|
__NEXT_DATA__: {
|
||||||
|
@ -51,7 +52,7 @@ export async function render (props, onError = renderErrorComponent) {
|
||||||
|
|
||||||
async function renderErrorComponent (err) {
|
async function renderErrorComponent (err) {
|
||||||
const { pathname, query } = router
|
const { pathname, query } = router
|
||||||
const props = await getInitialProps(ErrorComponent, { err, pathname, query })
|
const props = await loadGetInitialProps(ErrorComponent, { err, pathname, query })
|
||||||
await doRender({ Component: ErrorComponent, props, err })
|
await doRender({ Component: ErrorComponent, props, err })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ async function doRender ({ Component, props, err }) {
|
||||||
lastAppProps.Component === ErrorComponent) {
|
lastAppProps.Component === ErrorComponent) {
|
||||||
// fetch props if ErrorComponent was replaced with a page component by HMR
|
// fetch props if ErrorComponent was replaced with a page component by HMR
|
||||||
const { pathname, query } = router
|
const { pathname, query } = router
|
||||||
props = await getInitialProps(Component, { err, pathname, query })
|
props = await loadGetInitialProps(Component, { err, pathname, query })
|
||||||
}
|
}
|
||||||
|
|
||||||
Component = Component || lastAppProps.Component
|
Component = Component || lastAppProps.Component
|
||||||
|
@ -71,7 +72,3 @@ async function doRender ({ Component, props, err }) {
|
||||||
lastAppProps = appProps
|
lastAppProps = appProps
|
||||||
ReactDOM.render(createElement(App, appProps), container)
|
ReactDOM.render(createElement(App, appProps), container)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialProps (Component, ctx) {
|
|
||||||
return Component.getInitialProps ? Component.getInitialProps(ctx) : {}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,10 +3,11 @@ import Header from '../components/Header'
|
||||||
|
|
||||||
export default class About extends Component {
|
export default class About extends Component {
|
||||||
// Add some delay
|
// Add some delay
|
||||||
static getInitialProps () {
|
static async getInitialProps () {
|
||||||
return new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500)
|
setTimeout(resolve, 500)
|
||||||
})
|
})
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -3,10 +3,11 @@ import Header from '../components/Header'
|
||||||
|
|
||||||
export default class Forever extends Component {
|
export default class Forever extends Component {
|
||||||
// Add some delay
|
// Add some delay
|
||||||
static getInitialProps () {
|
static async getInitialProps () {
|
||||||
return new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 3000)
|
setTimeout(resolve, 3000)
|
||||||
})
|
})
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import evalScript from '../eval-script'
|
||||||
import shallowEquals from '../shallow-equals'
|
import shallowEquals from '../shallow-equals'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { reloadIfPrefetched } from '../prefetch'
|
import { reloadIfPrefetched } from '../prefetch'
|
||||||
|
import { loadGetInitialProps } from '../utils'
|
||||||
|
|
||||||
export default class Router extends EventEmitter {
|
export default class Router extends EventEmitter {
|
||||||
constructor (pathname, query, { Component, ErrorComponent, err } = {}) {
|
constructor (pathname, query, { Component, ErrorComponent, err } = {}) {
|
||||||
|
@ -234,7 +235,7 @@ export default class Router extends EventEmitter {
|
||||||
const cancel = () => { cancelled = true }
|
const cancel = () => { cancelled = true }
|
||||||
this.componentLoadCancel = cancel
|
this.componentLoadCancel = cancel
|
||||||
|
|
||||||
const props = await (Component.getInitialProps ? Component.getInitialProps(ctx) : {})
|
const props = await loadGetInitialProps(Component, ctx)
|
||||||
|
|
||||||
if (cancel === this.componentLoadCancel) {
|
if (cancel === this.componentLoadCancel) {
|
||||||
this.componentLoadCancel = null
|
this.componentLoadCancel = null
|
||||||
|
|
12
lib/utils.js
12
lib/utils.js
|
@ -41,3 +41,15 @@ export function printAndExit (message, code = 1) {
|
||||||
|
|
||||||
process.exit(code)
|
process.exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadGetInitialProps (Component, ctx) {
|
||||||
|
if (!Component.getInitialProps) return {}
|
||||||
|
|
||||||
|
const props = await Component.getInitialProps(ctx)
|
||||||
|
if (!props) {
|
||||||
|
const compName = Component.displayName || Component.name
|
||||||
|
const message = `"${compName}.getInitialProps()" should resolve to an object. But found "${props}" instead.`
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import requireModule from './require'
|
||||||
import resolvePath from './resolve'
|
import resolvePath from './resolve'
|
||||||
import readPage from './read-page'
|
import readPage from './read-page'
|
||||||
import { Router } from '../lib/router'
|
import { Router } from '../lib/router'
|
||||||
|
import { loadGetInitialProps } from '../lib/utils'
|
||||||
import Head, { defaultHead } from '../lib/head'
|
import Head, { defaultHead } from '../lib/head'
|
||||||
import App from '../lib/app'
|
import App from '../lib/app'
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ async function doRender (req, res, pathname, query, {
|
||||||
component,
|
component,
|
||||||
errorComponent
|
errorComponent
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
Component.getInitialProps ? Component.getInitialProps(ctx) : {},
|
loadGetInitialProps(Component, ctx),
|
||||||
readPage(join(dir, '.next', 'bundles', 'pages', page)),
|
readPage(join(dir, '.next', 'bundles', 'pages', page)),
|
||||||
readPage(join(dir, '.next', 'bundles', 'pages', '_error'))
|
readPage(join(dir, '.next', 'bundles', 'pages', '_error'))
|
||||||
])
|
])
|
||||||
|
@ -80,7 +81,7 @@ async function doRender (req, res, pathname, query, {
|
||||||
return { html, head }
|
return { html, head }
|
||||||
}
|
}
|
||||||
|
|
||||||
const docProps = await Document.getInitialProps({ ...ctx, renderPage })
|
const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage })
|
||||||
|
|
||||||
const doc = createElement(Document, {
|
const doc = createElement(Document, {
|
||||||
__NEXT_DATA__: {
|
__NEXT_DATA__: {
|
||||||
|
|
4
test/integration/basic/pages/empty-get-initial-props.js
Normal file
4
test/integration/basic/pages/empty-get-initial-props.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const EmptyInitialPropsPage = () => (<div>My Page</div>)
|
||||||
|
EmptyInitialPropsPage.getInitialProps = () => null
|
||||||
|
|
||||||
|
export default EmptyInitialPropsPage
|
|
@ -3,7 +3,7 @@ import Link from 'next/link'
|
||||||
export default () => (
|
export default () => (
|
||||||
<div className='nav-about'>
|
<div className='nav-about'>
|
||||||
<Link href='/nav'>
|
<Link href='/nav'>
|
||||||
<a>Go Back</a>
|
<a id='home-link'>Go Back</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<p>This is the about page.</p>
|
<p>This is the about page.</p>
|
||||||
|
|
|
@ -3,6 +3,10 @@ import { Component } from 'react'
|
||||||
|
|
||||||
let counter = 0
|
let counter = 0
|
||||||
|
|
||||||
|
const linkStyle = {
|
||||||
|
marginRight: 10
|
||||||
|
}
|
||||||
|
|
||||||
export default class extends Component {
|
export default class extends Component {
|
||||||
|
|
||||||
increase () {
|
increase () {
|
||||||
|
@ -13,7 +17,8 @@ export default class extends Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className='nav-home'>
|
<div className='nav-home'>
|
||||||
<Link href='/nav/about'><a>About</a></Link>
|
<Link href='/nav/about'><a id='about-link' style={linkStyle}>About</a></Link>
|
||||||
|
<Link href='/empty-get-initial-props'><a id='empty-props' style={linkStyle}>Empty Props</a></Link>
|
||||||
<p>This is the home.</p>
|
<p>This is the home.</p>
|
||||||
<div id='counter'>
|
<div id='counter'>
|
||||||
Counter: {counter}
|
Counter: {counter}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default (context) => {
|
||||||
it('should navigate the page', async () => {
|
it('should navigate the page', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nav')
|
const browser = await webdriver(context.appPort, '/nav')
|
||||||
const text = await browser
|
const text = await browser
|
||||||
.elementByCss('a').click()
|
.elementByCss('#about-link').click()
|
||||||
.waitForElementByCss('.nav-about')
|
.waitForElementByCss('.nav-about')
|
||||||
.elementByCss('p').text()
|
.elementByCss('p').text()
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ export default (context) => {
|
||||||
|
|
||||||
const counterText = await browser
|
const counterText = await browser
|
||||||
.elementByCss('#increase').click()
|
.elementByCss('#increase').click()
|
||||||
.elementByCss('a').click()
|
.elementByCss('#about-link').click()
|
||||||
.waitForElementByCss('.nav-about')
|
.waitForElementByCss('.nav-about')
|
||||||
.elementByCss('a').click()
|
.elementByCss('#home-link').click()
|
||||||
.waitForElementByCss('.nav-home')
|
.waitForElementByCss('.nav-home')
|
||||||
.elementByCss('#counter').text()
|
.elementByCss('#counter').text()
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export default (context) => {
|
||||||
it('should navigate the page', async () => {
|
it('should navigate the page', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nav/about')
|
const browser = await webdriver(context.appPort, '/nav/about')
|
||||||
const text = await browser
|
const text = await browser
|
||||||
.elementByCss('a').click()
|
.elementByCss('#home-link').click()
|
||||||
.waitForElementByCss('.nav-home')
|
.waitForElementByCss('.nav-home')
|
||||||
.elementByCss('p').text()
|
.elementByCss('p').text()
|
||||||
|
|
||||||
|
@ -44,5 +44,20 @@ export default (context) => {
|
||||||
await browser.close()
|
await browser.close()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with empty getInitialProps()', () => {
|
||||||
|
it('should render an error', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/nav')
|
||||||
|
const preText = await browser
|
||||||
|
.elementByCss('#empty-props').click()
|
||||||
|
.waitForElementByCss('pre')
|
||||||
|
.elementByCss('pre').text()
|
||||||
|
|
||||||
|
const expectedErrorMessage = '"EmptyInitialPropsPage.getInitialProps()" should resolve to an object. But found "null" instead.'
|
||||||
|
expect(preText.includes(expectedErrorMessage)).toBeTruthy()
|
||||||
|
|
||||||
|
await browser.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,12 @@ export default function ({ app }, suiteName, render) {
|
||||||
expect(link.text()).toBe('About')
|
expect(link.text()).toBe('About')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('getInitialProps resolves to null', async () => {
|
||||||
|
const $ = await get$('/empty-get-initial-props')
|
||||||
|
const expectedErrorMessage = '"EmptyInitialPropsPage.getInitialProps()" should resolve to an object. But found "null" instead.'
|
||||||
|
expect($('pre').text().includes(expectedErrorMessage)).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
test('error', async () => {
|
test('error', async () => {
|
||||||
const $ = await get$('/error')
|
const $ = await get$('/error')
|
||||||
expect($('pre').text()).toMatch(/This is an expected error/)
|
expect($('pre').text()).toMatch(/This is an expected error/)
|
||||||
|
|
Loading…
Reference in a new issue