14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-08-28

これを出したい

Screen_Shot_2019-08-28_at_14_21_22.png

探した内容

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

withApollo.tsx

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

...
...

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

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

...
...

下の方にあるfunction createApolloClient内の

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

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

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を使う。

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

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

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

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

export default withApollo(App)

[]:
[]:
[]:

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?