Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Examples] Move with-graphql-hooks to SSG #13858

Merged
merged 3 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/with-apollo/lib/apolloClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 0 additions & 2 deletions examples/with-graphql-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
52 changes: 27 additions & 25 deletions examples/with-graphql-hooks/components/header.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import Link from 'next/link'
import { withRouter } from 'next/router'
import { useRouter } from 'next/router'

const Header = ({ router: { pathname } }) => (
<header>
<Link href="/">
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
</Link>
<Link href="/about">
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
</Link>
<style jsx>{`
header {
margin-bottom: 25px;
}
a {
font-size: 14px;
margin-right: 15px;
text-decoration: none;
}
.is-active {
text-decoration: underline;
}
`}</style>
</header>
)
export default function Header() {
const { pathname } = useRouter()

export default withRouter(Header)
return (
<header>
<Link href="/">
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
</Link>
<Link href="/about">
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
</Link>
<style jsx>{`
header {
margin-bottom: 25px;
}
a {
font-size: 14px;
margin-right: 15px;
text-decoration: none;
}
.is-active {
text-decoration: underline;
}
`}</style>
</header>
)
}
29 changes: 17 additions & 12 deletions examples/with-graphql-hooks/components/post-list.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 <ErrorMessage message="Error loading posts." />
if (!data) return <div>Loading</div>
Expand All @@ -38,7 +43,7 @@ export default function PostList() {

const areMorePosts = allPosts.length < _allPostsMeta.count
return (
<Fragment>
<>
<Submit
onSubmission={() => {
refetch({ variables: { skip: 0, first: allPosts.length } })
Expand Down Expand Up @@ -109,6 +114,6 @@ export default function PostList() {
}
`}</style>
</section>
</Fragment>
</>
)
}
40 changes: 40 additions & 0 deletions examples/with-graphql-hooks/lib/graphql-client.js
Original file line number Diff line number Diff line change
@@ -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
}
30 changes: 30 additions & 0 deletions examples/with-graphql-hooks/lib/graphql-request.js
Original file line number Diff line number Diff line change
@@ -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
}
Comment on lines +9 to +30
Copy link
Member Author

@lfades lfades Jun 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bmullan91 Would be nice to have a better alternative to do this directly in graphql-hooks

Context: graphql-hooks-ssr doesn't work well with data fetching methods in Next.js, as we don't give access to the App component, instead the query or queries that are required to be server rendered get requested with this method before render, and the data is later added (by using the cache) to the GraphQL client in the browser as initial state.

27 changes: 0 additions & 27 deletions examples/with-graphql-hooks/lib/init-graphql.js

This file was deleted.

60 changes: 0 additions & 60 deletions examples/with-graphql-hooks/lib/with-graphql-client.js

This file was deleted.

7 changes: 0 additions & 7 deletions examples/with-graphql-hooks/package.json
Original file line number Diff line number Diff line change
@@ -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
}
}
22 changes: 9 additions & 13 deletions examples/with-graphql-hooks/pages/_app.js
Original file line number Diff line number Diff line change
@@ -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 (
<ClientContext.Provider value={graphQLClient}>
<Component {...pageProps} />
</ClientContext.Provider>
)
}
}
export default function App({ Component, pageProps }) {
const graphQLClient = useGraphQLClient(pageProps.initialGraphQLState)

export default withGraphQLClient(MyApp)
return (
<ClientContext.Provider value={graphQLClient}>
<Component {...pageProps} />
</ClientContext.Provider>
)
}
20 changes: 19 additions & 1 deletion examples/with-graphql-hooks/pages/index.js
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -10,3 +15,16 @@ export default function Home() {
</App>
)
}

export async function getStaticProps() {
const client = initializeGraphQL()

await graphQLRequest(client, allPostsQuery, allPostsQueryOptions())

return {
props: {
initialGraphQLState: client.cache.getInitialState(),
},
unstable_revalidate: 1,
}
}