124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
import React, {ErrorInfo} from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { execOnce, loadGetInitialProps, NextComponentType, IContext, IAppContext, IAppInitialProps, IAppProps } from 'next-server/dist/lib/utils'
|
|
import { makePublicRouterInstance } from '../client/router'
|
|
|
|
export { NextComponentType, IContext, IAppContext, IAppInitialProps, IAppProps }
|
|
|
|
type Router = import('next-server/dist/lib/router/router').default
|
|
|
|
export type AppClientContext = IAppContext<Router>
|
|
|
|
export type AppProps = IAppProps<Router>
|
|
|
|
export default class App extends React.Component<AppProps> {
|
|
static childContextTypes = {
|
|
router: PropTypes.object,
|
|
}
|
|
|
|
static async getInitialProps({ Component, ctx }: AppClientContext): Promise<IAppInitialProps> {
|
|
const pageProps = await loadGetInitialProps(Component, ctx)
|
|
return { pageProps }
|
|
}
|
|
|
|
getChildContext() {
|
|
return {
|
|
router: makePublicRouterInstance(this.props.router),
|
|
}
|
|
}
|
|
|
|
// Kept here for backwards compatibility.
|
|
// When someone ended App they could call `super.componentDidCatch`.
|
|
// @deprecated This method is no longer needed. Errors are caught at the top level
|
|
componentDidCatch(error: Error, _errorInfo: ErrorInfo): void {
|
|
throw error
|
|
}
|
|
|
|
render() {
|
|
const { router, Component, pageProps } = this.props
|
|
const url = createUrl(router)
|
|
return (
|
|
<Container>
|
|
<Component {...pageProps} url={url} />
|
|
</Container>
|
|
)
|
|
}
|
|
}
|
|
|
|
export class Container extends React.Component {
|
|
componentDidMount() {
|
|
this.scrollToHash()
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
this.scrollToHash()
|
|
}
|
|
|
|
private scrollToHash() {
|
|
let { hash } = window.location
|
|
hash = hash && hash.substring(1)
|
|
if (!hash) return
|
|
|
|
const el = document.getElementById(hash)
|
|
if (!el) return
|
|
|
|
// If we call scrollIntoView() in here without a setTimeout
|
|
// it won't scroll properly.
|
|
setTimeout(() => el.scrollIntoView(), 0)
|
|
}
|
|
|
|
render() {
|
|
return this.props.children
|
|
}
|
|
}
|
|
|
|
const warnUrl = execOnce(() => {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.error(`Warning: the 'url' property is deprecated. https://err.sh/zeit/next.js/url-deprecated`)
|
|
}
|
|
})
|
|
|
|
export function createUrl(router: Router) {
|
|
// This is to make sure we don't references the router object at call time
|
|
const { pathname, asPath, query } = router
|
|
return {
|
|
get query() {
|
|
warnUrl()
|
|
return query
|
|
},
|
|
get pathname() {
|
|
warnUrl()
|
|
return pathname
|
|
},
|
|
get asPath() {
|
|
warnUrl()
|
|
return asPath
|
|
},
|
|
back: () => {
|
|
warnUrl()
|
|
router.back()
|
|
},
|
|
push: (url: string, as?: string) => {
|
|
warnUrl()
|
|
return router.push(url, as)
|
|
},
|
|
pushTo: (href: string, as?: string) => {
|
|
warnUrl()
|
|
const pushRoute = as ? href : ''
|
|
const pushUrl = as || href
|
|
|
|
return router.push(pushRoute, pushUrl)
|
|
},
|
|
replace: (url: string, as?: string) => {
|
|
warnUrl()
|
|
return router.replace(url, as)
|
|
},
|
|
replaceTo: (href: string, as?: string) => {
|
|
warnUrl()
|
|
const replaceRoute = as ? href : ''
|
|
const replaceUrl = as || href
|
|
|
|
return router.replace(replaceRoute, replaceUrl)
|
|
},
|
|
}
|
|
}
|