_app.tsx 3.1 KB
Newer Older
T
Tim Neutkens 已提交
1
import React, {ErrorInfo} from 'react'
A
Alexander Nanberg 已提交
2
import PropTypes from 'prop-types'
3
import { execOnce, loadGetInitialProps, AppContextType, AppInitialProps, AppPropsType } from 'next-server/dist/lib/utils'
4
import { Router, makePublicRouterInstance } from '../client/router'
5

6
export { AppInitialProps }
7

8
export type AppContext = AppContextType<Router>
9

10
export type AppProps<P = {}> = AppPropsType<Router, P>
11

J
JJ Kasper 已提交
12 13 14 15 16
async function appGetInitialProps({ Component, ctx }: AppContext): Promise<AppInitialProps> {
  const pageProps = await loadGetInitialProps(Component, ctx)
  return { pageProps }
}

17
export default class App<P = {}, CP = P> extends React.Component<P & AppProps<CP>> {
A
Alexander Nanberg 已提交
18
  static childContextTypes = {
19
    router: PropTypes.object,
A
Alexander Nanberg 已提交
20
  }
J
JJ Kasper 已提交
21 22
  static origGetInitialProps = appGetInitialProps
  static getInitialProps = appGetInitialProps
23

24
  getChildContext() {
A
Alexander Nanberg 已提交
25
    return {
26
      router: makePublicRouterInstance(this.props.router),
A
Alexander Nanberg 已提交
27 28 29
    }
  }

30
  // Kept here for backwards compatibility.
T
Tim Neutkens 已提交
31 32 33 34
  // 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
35 36
  }

37
  render() {
38
    const { router, Component, pageProps } = this.props as AppProps<CP>
39
    const url = createUrl(router)
40 41 42 43 44
    return (
      <Container>
        <Component {...pageProps} url={url} />
      </Container>
    )
45 46 47
  }
}

48 49
export class Container extends React.Component {
  componentDidMount() {
50 51 52
    this.scrollToHash()
  }

53
  componentDidUpdate() {
54 55 56
    this.scrollToHash()
  }

T
Tim Neutkens 已提交
57
  private scrollToHash() {
58
    let { hash } = window.location
59
    hash = hash && hash.substring(1)
60 61 62 63 64 65 66 67 68 69
    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)
  }

70
  render() {
71 72 73 74 75 76 77 78 79 80
    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`)
  }
})

81
export function createUrl(router: Router) {
82
  // This is to make sure we don't references the router object at call time
83
  const { pathname, asPath, query } = router
84
  return {
85
    get query() {
86 87 88
      warnUrl()
      return query
    },
89
    get pathname() {
90 91 92
      warnUrl()
      return pathname
    },
93
    get asPath() {
94 95 96 97 98 99 100
      warnUrl()
      return asPath
    },
    back: () => {
      warnUrl()
      router.back()
    },
101
    push: (url: string, as?: string) => {
102 103 104
      warnUrl()
      return router.push(url, as)
    },
105
    pushTo: (href: string, as?: string) => {
106
      warnUrl()
107
      const pushRoute = as ? href : ''
108 109 110 111
      const pushUrl = as || href

      return router.push(pushRoute, pushUrl)
    },
112
    replace: (url: string, as?: string) => {
113 114 115
      warnUrl()
      return router.replace(url, as)
    },
116
    replaceTo: (href: string, as?: string) => {
117
      warnUrl()
118
      const replaceRoute = as ? href : ''
119 120 121
      const replaceUrl = as || href

      return router.replace(replaceRoute, replaceUrl)
122
    },
123 124
  }
}