_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, NextComponentType, IContext, IAppContext, IAppInitialProps, IAppProps } from 'next-server/dist/lib/utils'
T
Tim Neutkens 已提交
4
import { makePublicRouterInstance } from '../client/router'
5

6 7
export { NextComponentType, IContext, IAppContext, IAppInitialProps, IAppProps }

8
type Router = import('next-server/dist/lib/router/router').default
T
Tim Neutkens 已提交
9

10 11 12 13 14
export type AppClientContext = IAppContext<Router>

export type AppProps = IAppProps<Router>

export default class App extends React.Component<AppProps> {
A
Alexander Nanberg 已提交
15
  static childContextTypes = {
16
    router: PropTypes.object,
A
Alexander Nanberg 已提交
17 18
  }

19
  static async getInitialProps({ Component, ctx }: AppClientContext): Promise<IAppInitialProps> {
20
    const pageProps = await loadGetInitialProps(Component, ctx)
21
    return { pageProps }
22 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
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
  }
}