Next.js[v9]のgetInitialPropsの中でapolloを使いたい、しかもTypescriptで

# これを出したい 

![Screen_Shot_2019-08-28_at_14_21_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16757/10207847-6510-1ee9-9e6b-a1f073f24b26.png)


# 探した内容

1. [next-with-apollo][1]を見つけるが、[Support hooks #74][2]の中でapollo-hookも使いたいなら、[next-with-apollo][1]は使わずに[with-apollo/lib/apollo.js][3]これをベースにした方が良い、とある
2. [with-apollo/lib/apollo.js][3]がjsベースなのでtsベースにするために色々探す
3. [How would you type this?][4]を見つけて参考にしてみる
4. 因みにv8以前まで`_app.js`を作る時に`Container`って言うのも使っていたけど、今では**[deprecated][5]**
5. 2.、3.をベースに`withApollo.tsx`を考えてみる


# withApollo.tsx

基本[with-apollo/lib/apollo.js][3]と同じだが、`PageComponent.getInitialProps`に渡す時の`ctx`に`apolloClient`を生やしている。

```tsx
...
...

const apolloClient = initApolloClient()
ctx.ctx.apolloClient = apolloClient

let pageProps = {}
if (PageComponent.getInitialProps) {
  pageProps = await PageComponent.getInitialProps(ctx)
}

...
...
```

下の方にある`function createApolloClient`内の

```tsx
uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn", 
```

は自分のサーバーへ変更必要。

```tsx:withApollo.tsx
import React, { useMemo } from "react"
import Head from "next/head"
import { ApolloProvider } from "@apollo/react-hooks"
import { InMemoryCache } from "apollo-cache-inmemory"
import { ApolloClient } from "apollo-client"
import { HttpLink } from "apollo-link-http"
import fetch from "isomorphic-unfetch"

let apolloClient = null

/**
 * Creates and provides the apolloContext
 * to a next.js PageTree. Use it by wrapping
 * your PageComponent via HOC pattern.
 * @param {Function|Class} PageComponent
 * @param {Object} [config]
 * @param {Boolean} [config.ssr=true]
 */
export function withApollo(PageComponent, { ssr = true } = {}) {
  const WithApollo = ({
    apolloClient,
    apolloState,
    ...pageProps
  }: {
    apolloClient: ApolloClient<{}>
    apolloState: any
    [key: string]: any
  }) => {
    const client = useMemo(
      () => apolloClient || initApolloClient(apolloState),
      []
    )
    return (
      <ApolloProvider client={client}>
        <PageComponent {...pageProps} />
      </ApolloProvider>
    )
  }

  // Set the correct displayName in development
  if (process.env.NODE_ENV !== "production") {
    const displayName =
      PageComponent.displayName || PageComponent.name || "Component"

    if (displayName === "App") {
      console.warn("This withApollo HOC only works with PageComponents.")
    }

    WithApollo.displayName = `withApollo(${displayName})`
  }

  // Allow Next.js to remove getInitialProps from the browser build
  if (typeof window === "undefined") {
    if (ssr) {
      WithApollo.getInitialProps = async ctx => {
        const { AppTree } = ctx
        // Run all GraphQL queries in the component tree
        // and extract the resulting data
        const apolloClient = initApolloClient()
        ctx.ctx.apolloClient = apolloClient

        let pageProps = {}
        if (PageComponent.getInitialProps) {
          pageProps = await PageComponent.getInitialProps(ctx)
        }

        try {
          // Run all GraphQL queries
          await require("@apollo/react-ssr").getDataFromTree(
            <AppTree
              pageProps={{
                ...pageProps,
                apolloClient,
              }}
            />
          )
        } catch (error) {
          // Prevent Apollo Client GraphQL errors from crashing SSR.
          // Handle them in components via the data.error prop:
          // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
          console.error("Error while running `getDataFromTree`", error)
        }

        // getDataFromTree does not call componentWillUnmount
        // head side effect therefore need to be cleared manually
        Head.rewind()

        // Extract query data from the Apollo store
        const apolloState = apolloClient.cache.extract()

        return {
          ...pageProps,
          apolloState,
        }
      }
    }
  }

  return WithApollo
}

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {Object} initialState
 */
function initApolloClient(initialState = {}): ApolloClient<{}> {
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (typeof window === "undefined") {
    return createApolloClient(initialState)
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = createApolloClient(initialState)
  }

  return apolloClient
}

/**
 * Creates and configures the ApolloClient
 * @param  {Object} [initialState={}]
 */
function createApolloClient(initialState = {}): ApolloClient<{}> {
  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  const isBrowser = typeof window !== "undefined"
  return new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
    link: new HttpLink({
      // このuriは各自変更必要
      uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn", // Server URL (must be absolute)
      credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
      // Use fetch() polyfill on the server
      fetch: !isBrowser && fetch,
    }),
    cache: new InMemoryCache().restore(initialState),
  })
}

```

## Page
実際に`/pages`配下で使う時は`NextPage`をextendした`apolloClient`を盛り込んだ独自のPageを使う。

```ts:NextPageWithApolloClient.ts
import { NextPage, NextPageContext } from "next"
import { ApolloClient } from "apollo-client"

export interface NextPageWithApolloClient<P = {}, IP = P> extends NextPage<P, IP> {
  getInitialProps?(ctx: NextPageContext & { apolloClient: ApolloClient<{}> }): Promise<IP>
}

```

```tsx:index.tsx

interface Props {
  results: Array<any>
}
// NextPageWithApolloClient を使う
const Index: NextPageWithApolloClient<Props> = props => {
  console.log('props:', props)
  return <>index</>
}
Index.getInitialProps = async ({apolloClient}) => {
                                 // awaitを忘れずに
  const {data, loading, error} = await apolloClient.query({query: QUERY})

  // interface Propsと合わせる必要がある
  return {
    results: []
  }
}
export default Index
```

## _app.tsx

`ApolloProvider`は`withApollo`内で既に作られているので、ここまでシンプルになる。
（もしくはそもそも`_app.tsx`は作らず、apolloが必要な`Page`だけで`withApollo`を使う）

```tsx:_app.tsx
import App from "next/app"
import { withApollo } from "../lib/withApollo"

export default withApollo(App)

```




[1]: https://github.com/lfades/next-with-apollo
[2]: https://github.com/lfades/next-with-apollo/issues/74#issuecomment-525345975
[3]: https://github.com/zeit/next.js/blob/18657a459967299229cf5962b548abd3405a3d42/examples/with-apollo/lib/apollo.js
[4]: https://spectrum.chat/next-js/general/how-would-you-type-this~662bfdb5-b174-4503-94a6-e6ad83bca204
[5]: https://github.com/zeit/next.js/blob/cffa2e3f2165b58b77fb354d3f74a4ac8222b68f/packages/next/pages/_app.tsx#L60
[]: 
[]: 
[]: