1
0
Fork 0
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:
Arunoda Susiripala 2017-01-20 11:33:46 -08:00 committed by Guillermo Rauch
parent 318f110a8e
commit 399e510389
11 changed files with 62 additions and 19 deletions

View file

@ -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) : {}
}

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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

View file

@ -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
}

View file

@ -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__: {

View file

@ -0,0 +1,4 @@
const EmptyInitialPropsPage = () => (<div>My Page</div>)
EmptyInitialPropsPage.getInitialProps = () => null
export default EmptyInitialPropsPage

View file

@ -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>

View file

@ -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}

View file

@ -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()
})
})
}) })
} }

View file

@ -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/)