diff --git a/examples/api-routes-apollo-server-and-client/apollo/client.js b/examples/api-routes-apollo-server-and-client/apollo/client.js index 26ae378500bd..dd890d1d3ffd 100644 --- a/examples/api-routes-apollo-server-and-client/apollo/client.js +++ b/examples/api-routes-apollo-server-and-client/apollo/client.js @@ -30,7 +30,7 @@ export function initializeApollo(initialState = null) { const _apolloClient = apolloClient ?? createApolloClient() // If your page has Next.js data fetching methods that use Apollo Client, the initial state - // get hydrated here + // gets hydrated here if (initialState) { _apolloClient.cache.restore(initialState) } diff --git a/examples/with-apollo/lib/apolloClient.js b/examples/with-apollo/lib/apolloClient.js index 77ee7a59b114..18c52651590c 100644 --- a/examples/with-apollo/lib/apolloClient.js +++ b/examples/with-apollo/lib/apolloClient.js @@ -20,7 +20,7 @@ export function initializeApollo(initialState = null) { const _apolloClient = apolloClient ?? createApolloClient() // If your page has Next.js data fetching methods that use Apollo Client, the initial state - // get hydrated here + // gets hydrated here if (initialState) { _apolloClient.cache.restore(initialState) } diff --git a/examples/with-graphql-hooks/README.md b/examples/with-graphql-hooks/README.md index ae120414a144..e4aef1b537da 100644 --- a/examples/with-graphql-hooks/README.md +++ b/examples/with-graphql-hooks/README.md @@ -12,8 +12,6 @@ Deploy the example using [Vercel](https://vercel.com): [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-graphql-hooks) -_Live Example: https://next-with-graphql-hooks.now.sh_ - ## How to use ### Using `create-next-app` diff --git a/examples/with-graphql-hooks/components/header.js b/examples/with-graphql-hooks/components/header.js index 44be0c39b5ab..f789f939acd5 100644 --- a/examples/with-graphql-hooks/components/header.js +++ b/examples/with-graphql-hooks/components/header.js @@ -1,28 +1,30 @@ import Link from 'next/link' -import { withRouter } from 'next/router' +import { useRouter } from 'next/router' -const Header = ({ router: { pathname } }) => ( -
- - Home - - - About - - -
-) +export default function Header() { + const { pathname } = useRouter() -export default withRouter(Header) + return ( +
+ + Home + + + About + + +
+ ) +} diff --git a/examples/with-graphql-hooks/components/post-list.js b/examples/with-graphql-hooks/components/post-list.js index fce9e43e2f18..45707ece6fec 100644 --- a/examples/with-graphql-hooks/components/post-list.js +++ b/examples/with-graphql-hooks/components/post-list.js @@ -1,4 +1,4 @@ -import React, { Fragment, useState } from 'react' +import { useState } from 'react' import { useQuery } from 'graphql-hooks' import ErrorMessage from './error-message' import PostUpvoter from './post-upvoter' @@ -19,17 +19,22 @@ export const allPostsQuery = ` } ` +export const allPostsQueryOptions = (skip = 0) => ({ + variables: { skip, first: 10 }, + updateData: (prevResult, result) => ({ + ...result, + allPosts: prevResult + ? [...prevResult.allPosts, ...result.allPosts] + : result.allPosts, + }), +}) + export default function PostList() { const [skip, setSkip] = useState(0) - const { loading, error, data, refetch } = useQuery(allPostsQuery, { - variables: { skip, first: 10 }, - updateData: (prevResult, result) => ({ - ...result, - allPosts: prevResult - ? [...prevResult.allPosts, ...result.allPosts] - : result.allPosts, - }), - }) + const { loading, error, data, refetch } = useQuery( + allPostsQuery, + allPostsQueryOptions(skip) + ) if (error) return if (!data) return
Loading
@@ -38,7 +43,7 @@ export default function PostList() { const areMorePosts = allPosts.length < _allPostsMeta.count return ( - + <> { refetch({ variables: { skip: 0, first: allPosts.length } }) @@ -109,6 +114,6 @@ export default function PostList() { } `} - + ) } diff --git a/examples/with-graphql-hooks/lib/graphql-client.js b/examples/with-graphql-hooks/lib/graphql-client.js new file mode 100644 index 000000000000..e7d8ad825849 --- /dev/null +++ b/examples/with-graphql-hooks/lib/graphql-client.js @@ -0,0 +1,40 @@ +import { useMemo } from 'react' +import { GraphQLClient } from 'graphql-hooks' +import memCache from 'graphql-hooks-memcache' + +let graphQLClient + +function createClient(initialState) { + return new GraphQLClient({ + ssrMode: typeof window === 'undefined', + url: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute) + cache: memCache({ initialState }), + }) +} + +export function initializeGraphQL(initialState = null) { + const _graphQLClient = graphQLClient ?? createClient(initialState) + + // After navigating to a page with an initial GraphQL state, create a new cache with the + // current state merged with the incoming state and set it to the GraphQL client. + // This is necessary because the initial state of `memCache` can only be set once + if (initialState && graphQLClient) { + graphQLClient.cache = memCache({ + initialState: Object.assign( + graphQLClient.cache.getInitialState(), + initialState + ), + }) + } + // For SSG and SSR always create a new GraphQL Client + if (typeof window === 'undefined') return _graphQLClient + // Create the GraphQL Client once in the client + if (!graphQLClient) graphQLClient = _graphQLClient + + return _graphQLClient +} + +export function useGraphQLClient(initialState) { + const store = useMemo(() => initializeGraphQL(initialState), [initialState]) + return store +} diff --git a/examples/with-graphql-hooks/lib/graphql-request.js b/examples/with-graphql-hooks/lib/graphql-request.js new file mode 100644 index 000000000000..130da12c1523 --- /dev/null +++ b/examples/with-graphql-hooks/lib/graphql-request.js @@ -0,0 +1,30 @@ +const defaultOpts = { useCache: true } +/** + * Returns the result of a GraphQL query. It also adds the result to the + * cache of the GraphQL client for better initial data population in pages. + * + * Note: This helper tries to imitate what the query hooks of `graphql-hooks` + * do internally to make sure we generate the same cache key + */ +export default async function graphQLRequest(client, query, options) { + const opts = { ...defaultOpts, ...options } + const operation = { + query, + variables: opts.variables, + operationName: opts.operationName, + persisted: opts.persisted, + } + + if (opts.persisted || (client.useGETForQueries && !opts.isMutation)) { + opts.fetchOptionsOverrides = { + ...opts.fetchOptionsOverrides, + method: 'GET', + } + } + + const cacheKey = client.getCacheKey(operation, opts) + const cacheValue = await client.request(operation, opts) + + client.saveCache(cacheKey, cacheValue) + return cacheValue +} diff --git a/examples/with-graphql-hooks/lib/init-graphql.js b/examples/with-graphql-hooks/lib/init-graphql.js deleted file mode 100644 index 09492dac4920..000000000000 --- a/examples/with-graphql-hooks/lib/init-graphql.js +++ /dev/null @@ -1,27 +0,0 @@ -import { GraphQLClient } from 'graphql-hooks' -import memCache from 'graphql-hooks-memcache' - -let graphQLClient = null - -function create(initialState = {}) { - return new GraphQLClient({ - ssrMode: typeof window === 'undefined', - url: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', - cache: memCache({ initialState }), - }) -} - -export default function initGraphQL(initialState) { - // 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 create(initialState) - } - - // Reuse client on the client-side - if (!graphQLClient) { - graphQLClient = create(initialState) - } - - return graphQLClient -} diff --git a/examples/with-graphql-hooks/lib/with-graphql-client.js b/examples/with-graphql-hooks/lib/with-graphql-client.js deleted file mode 100644 index 4f46c79c9bd6..000000000000 --- a/examples/with-graphql-hooks/lib/with-graphql-client.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react' -import initGraphQL from './init-graphql' -import Head from 'next/head' -import { getInitialState } from 'graphql-hooks-ssr' - -export default function withGraphqlClient(App) { - return class GraphQLHooks extends React.Component { - static displayName = 'GraphQLHooks(App)' - static async getInitialProps(ctx) { - const { AppTree } = ctx - - let appProps = {} - if (App.getInitialProps) { - appProps = await App.getInitialProps(ctx) - } - - // Run all GraphQL queries in the component tree - // and extract the resulting data - let graphQLState = {} - if (typeof window === 'undefined') { - const graphQLClient = initGraphQL() - try { - // Run all GraphQL queries - graphQLState = await getInitialState({ - App: , - client: graphQLClient, - }) - } catch (error) { - // Prevent GraphQL hooks client errors from crashing SSR. - // Handle them in components via the state.error prop: - // https://github.com/nearform/graphql-hooks#usequery - console.error('Error while running `getInitialState`', error) - } - - // getInitialState does not call componentWillUnmount - // head side effect therefore need to be cleared manually - Head.rewind() - } - - return { - ...appProps, - graphQLState, - } - } - - constructor(props) { - super(props) - if (props.graphQLClient) { - // During SSR the GraphQL client is provided as a prop - this.graphQLClient = props.graphQLClient - } else { - this.graphQLClient = initGraphQL(props.graphQLState) - } - } - - render() { - return - } - } -} diff --git a/examples/with-graphql-hooks/package.json b/examples/with-graphql-hooks/package.json index a0007dd1f25b..5ddbcccd06ef 100644 --- a/examples/with-graphql-hooks/package.json +++ b/examples/with-graphql-hooks/package.json @@ -1,25 +1,18 @@ { "name": "with-graphql-hooks", "version": "1.0.0", - "description": "", - "main": "index.js", "scripts": { "dev": "next", "build": "next build", "start": "next start" }, - "author": "", "license": "ISC", "dependencies": { "graphql-hooks": "^4.4.4", "graphql-hooks-memcache": "^1.3.2", - "graphql-hooks-ssr": "^1.1.5", "next": "latest", "prop-types": "^15.7.2", "react": "^16.8.2", "react-dom": "^16.8.2" - }, - "browser": { - "graphql-hooks-ssr": false } } diff --git a/examples/with-graphql-hooks/pages/_app.js b/examples/with-graphql-hooks/pages/_app.js index 446621c17319..9420b692f11e 100644 --- a/examples/with-graphql-hooks/pages/_app.js +++ b/examples/with-graphql-hooks/pages/_app.js @@ -1,16 +1,12 @@ -import App from 'next/app' -import withGraphQLClient from '../lib/with-graphql-client' import { ClientContext } from 'graphql-hooks' +import { useGraphQLClient } from '../lib/graphql-client' -class MyApp extends App { - render() { - const { Component, pageProps, graphQLClient } = this.props - return ( - - - - ) - } -} +export default function App({ Component, pageProps }) { + const graphQLClient = useGraphQLClient(pageProps.initialGraphQLState) -export default withGraphQLClient(MyApp) + return ( + + + + ) +} diff --git a/examples/with-graphql-hooks/pages/index.js b/examples/with-graphql-hooks/pages/index.js index bb92144340dc..0f7cd40deaa8 100644 --- a/examples/with-graphql-hooks/pages/index.js +++ b/examples/with-graphql-hooks/pages/index.js @@ -1,6 +1,11 @@ +import { initializeGraphQL } from '../lib/graphql-client' +import graphQLRequest from '../lib/graphql-request' import App from '../components/app' import Header from '../components/header' -import PostList from '../components/post-list' +import PostList, { + allPostsQuery, + allPostsQueryOptions, +} from '../components/post-list' export default function Home() { return ( @@ -10,3 +15,16 @@ export default function Home() { ) } + +export async function getStaticProps() { + const client = initializeGraphQL() + + await graphQLRequest(client, allPostsQuery, allPostsQueryOptions()) + + return { + props: { + initialGraphQLState: client.cache.getInitialState(), + }, + unstable_revalidate: 1, + } +}