これを出したい
探した内容
- next-with-apolloを見つけるが、Support hooks #74の中でapollo-hookも使いたいなら、next-with-apolloは使わずにwith-apollo/lib/apollo.jsこれをベースにした方が良い、とある
- with-apollo/lib/apollo.jsがjsベースなのでtsベースにするために色々探す
- How would you type this?を見つけて参考にしてみる
- 因みにv8以前まで
_app.jsを作る時にContainerって言うのも使っていたけど、今では**deprecated** - 2.、3.をベースに
withApollo.tsxを考えてみる
withApollo.tsx
基本with-apollo/lib/apollo.jsと同じだが、PageComponent.getInitialPropsに渡す時のctxにapolloClientを生やしている。
...
...
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
ApolloProviderはwithApollo内で既に作られているので、ここまでシンプルになる。
(もしくはそもそも_app.tsxは作らず、apolloが必要なPageだけでwithApolloを使う)
_app.tsx
import App from "next/app"
import { withApollo } from "../lib/withApollo"
export default withApollo(App)
[]:
[]:
[]:
