Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit 61e14ea

Browse files
authored
Merge pull request #34 from WebDevStudios/feature/apollo-wordpress-api-connection
Apollo Wordpress API Connection
2 parents a5d762f + 94dc70f commit 61e14ea

File tree

9 files changed

+322
-16
lines changed

9 files changed

+322
-16
lines changed

api/wordpress/connector.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {useMemo} from 'react'
2+
import {ApolloClient, HttpLink, InMemoryCache} from '@apollo/client'
3+
import merge from 'deepmerge'
4+
import isEqual from 'lodash/isEqual'
5+
import {getEnvVar} from '@/lib/functions'
6+
7+
// Define env vars.
8+
export const wpApiUrlBase = getEnvVar('WORDPRESS_API_URL')
9+
10+
// Set global state name.
11+
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'
12+
13+
let apolloClient
14+
15+
/**
16+
* Create an basic Apollo client.
17+
*
18+
* @see https://www.apollographql.com/docs/react/api/core/ApolloClient/
19+
*/
20+
export function createApolloClient() {
21+
return new ApolloClient({
22+
ssrMode: false,
23+
link: new HttpLink({
24+
uri: `${wpApiUrlBase}graphql`,
25+
credentials: ''
26+
}),
27+
cache: new InMemoryCache()
28+
})
29+
}
30+
31+
/**
32+
* Init Apollo and merge with initial state.
33+
*
34+
* @param {mixed} initialState The initial state of things.
35+
*/
36+
export function initializeApollo(initialState = null) {
37+
// Only run one instance of the Apollo client.
38+
const _apolloClient = apolloClient ?? createApolloClient()
39+
40+
// If a page has Next.js data fetching methods that
41+
// use Apollo Client, the initial state gets hydrated here.
42+
if (initialState) {
43+
// Get existing cache, loaded during client side data fetching.
44+
const existingCache = _apolloClient.extract()
45+
46+
// Merge the existing cache into data passed from getStaticProps()/getServerSideProps().
47+
const data = merge(initialState, existingCache, {
48+
// Combine arrays using object equality (like in sets).
49+
arrayMerge: (destinationArray, sourceArray) => [
50+
...sourceArray,
51+
...destinationArray.filter((d) =>
52+
sourceArray.every((s) => !isEqual(d, s))
53+
)
54+
]
55+
})
56+
57+
// Restore the cache with the merged data.
58+
_apolloClient.cache.restore(data)
59+
}
60+
61+
// For SSG and SSR always create a new Apollo Client.
62+
if (typeof window === 'undefined') return _apolloClient
63+
64+
// Create the Apollo Client once in the client.
65+
if (!apolloClient) apolloClient = _apolloClient
66+
67+
return _apolloClient
68+
}
69+
70+
/**
71+
* Pass down Apollo state to page props.
72+
*
73+
* @param {object} client Apollo Client props.
74+
* @param {object} pageProps Props from getStaticProps().
75+
*/
76+
export function addApolloState(client, pageProps) {
77+
if (pageProps?.props) {
78+
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract()
79+
}
80+
81+
return pageProps
82+
}
83+
84+
/**
85+
* Only update when the cache value has changed.
86+
*
87+
* @param {object} pageProps Props from getStaticProps().
88+
*/
89+
export function useApollo(pageProps) {
90+
const state = pageProps[APOLLO_STATE_PROP_NAME]
91+
const store = useMemo(() => initializeApollo(state), [state])
92+
return store
93+
}

api/wordpress/posts/AllPosts.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {gql, useQuery} from '@apollo/client'
2+
3+
export const ALL_POSTS_QUERY = gql`
4+
query allPosts {
5+
posts {
6+
edges {
7+
node {
8+
postId
9+
title
10+
slug
11+
}
12+
}
13+
}
14+
}
15+
`
16+
17+
/**
18+
* Query GraphQL and display the results.
19+
*
20+
* @see https://www.apollographql.com/docs/react/api/react/hooks/#usequery
21+
*/
22+
export default function AllPosts() {
23+
const {loading, error, data} = useQuery(ALL_POSTS_QUERY)
24+
25+
if (error) return <div>Error loading posts.</div>
26+
if (loading) return <div>Loading...</div>
27+
28+
const allPosts = data.posts.edges
29+
30+
return (
31+
<section>
32+
<ul>
33+
{allPosts.map((post) => {
34+
const {postId, slug, title} = post.node
35+
36+
return (
37+
<li key={postId}>
38+
<div>
39+
<a href={slug}>{title}</a>
40+
</div>
41+
</li>
42+
)
43+
})}
44+
</ul>
45+
</section>
46+
)
47+
}

jsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"compilerOptions": {
33
"baseUrl": ".",
44
"paths": {
5+
"@/api/*": ["api/*"],
56
"@/components/*": ["components/*"],
67
"@/lib/*": ["lib/*"],
78
"@/styles/*": ["styles/*"]

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const config = {
66
navigation: [
77
{label: 'Home', href: '/'},
88
{label: 'About', href: '/about'},
9+
{label: 'Apollo', href: '/apollo'},
910
{label: 'SSG', href: '/ssg'},
1011
{label: 'SSR', href: '/ssr'},
1112
{label: 'ISR', href: '/isr'}

lib/functions.js

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,34 @@
22
export const fetcher = (...args) => fetch(...args).then((res) => res.json()) // eslint-disable-line no-undef
33

44
/**
5-
* On scroll, add or remove a "shrink" class.
5+
* Retrieve environment-specific var.
66
*
7-
* @param {object} el The header element.
7+
* @param {string} varName Environment variable.
8+
* @param {bool} isPublic Whether var is public.
9+
* @return {string} Env var value.
810
*/
9-
export function shrinkHeader(el) {
10-
/* eslint-disable */
11-
window.addEventListener('scroll', () => {
12-
if (window.scrollY > 30) {
13-
el.current.classList.add('shrink')
14-
} else {
15-
el.current.classList.remove('shrink')
16-
}
17-
})
18-
/* eslint-enable */
11+
export function getEnvVar(varName, isPublic = false) {
12+
const prefix = isPublic ? 'NEXT_PUBLIC_' : ''
13+
14+
// If var missing or currently in Vercel "dev" (local), use local settings.
15+
if (!process.env.VERCEL_ENV || 'development' === process.env.VERCEL_ENV) {
16+
return process.env[`${prefix}LOCAL_${varName}`]
17+
}
18+
19+
// Prod / main / default.
20+
if (
21+
'production' === process.env.VERCEL_ENV ||
22+
'preview' !== process.env.VERCEL_ENV
23+
) {
24+
return process.env[`${prefix}PROD_${varName}`]
25+
}
26+
27+
// Switch between staging and develop in Vercel "preview" env.
28+
switch (process.env.VERCEL_GITHUB_COMMIT_REF) {
29+
case 'staging':
30+
return process.env[`${prefix}STAGING_${varName}`]
31+
case 'develop':
32+
default:
33+
return process.env[`${prefix}DEV_${varName}`]
34+
}
1935
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"react-dom": "17.0.1"
4242
},
4343
"devDependencies": {
44+
"@apollo/client": "^3.3.6",
4445
"@babel/core": "^7.12.10",
4546
"@storybook/addon-a11y": "^6.1.11",
4647
"@storybook/addon-actions": "^6.1.11",
@@ -49,13 +50,15 @@
4950
"@storybook/react": "^6.1.11",
5051
"babel-loader": "^8.2.2",
5152
"chromatic": "^5.5.0",
53+
"deepmerge": "^4.2.2",
5254
"eslint": "^7.16.0",
5355
"eslint-config-prettier": "^7.1.0",
5456
"eslint-plugin-import": "^2.22.1",
5557
"eslint-plugin-jsx-a11y": "^6.4.1",
5658
"eslint-plugin-prettier": "^3.3.0",
5759
"eslint-plugin-react": "^7.21.5",
5860
"eslint-plugin-react-hooks": "^4.2.0",
61+
"graphql": "^15.4.0",
5962
"husky": "^4.3.6",
6063
"lint-staged": "^10.5.3",
6164
"next-sitemap": "^1.3.22",

pages/_app.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import PropTypes from 'prop-types'
22
import '@/styles/index.css'
3+
import {ApolloProvider} from '@apollo/client'
4+
import {useApollo} from '@/api/wordpress/connector'
35

4-
const App = ({Component, pageProps}) => <Component {...pageProps} />
6+
export default function App({Component, pageProps}) {
7+
/**
8+
* Wrap the app in the ApolloProvider component.
9+
*
10+
* @see https://www.apollographql.com/docs/react/api/react/hooks/#the-apolloprovider-component
11+
*/
12+
const apolloClient = useApollo(pageProps)
13+
14+
return (
15+
<ApolloProvider client={apolloClient}>
16+
<Component {...pageProps} />
17+
</ApolloProvider>
18+
)
19+
}
520

621
App.propTypes = {
722
Component: PropTypes.any.isRequired,
823
pageProps: PropTypes.object.isRequired
924
}
10-
11-
export default App

pages/apollo.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Layout from '@/components/common/Layout'
2+
import AllPosts, {ALL_POSTS_QUERY} from '@/api/wordpress/posts/AllPosts'
3+
import {initializeApollo, addApolloState} from '@/api/wordpress/connector'
4+
5+
export default function ApolloPage() {
6+
return (
7+
<Layout
8+
title="Apollo Query"
9+
description="A static page that updates in the background every 60 seconds."
10+
>
11+
<div className="container">
12+
<h1>Apollo Query Example</h1>
13+
<AllPosts />
14+
</div>
15+
</Layout>
16+
)
17+
}
18+
19+
export async function getStaticProps() {
20+
// Create an Apollo instance.
21+
const apolloClient = initializeApollo()
22+
23+
// Run the query.
24+
await apolloClient.query({
25+
query: ALL_POSTS_QUERY
26+
})
27+
28+
// Merge in query results as Apollo state.
29+
return addApolloState(apolloClient, {
30+
props: {},
31+
revalidate: 60
32+
})
33+
}

0 commit comments

Comments
 (0)