Skip to content

Instantly share code, notes, and snippets.

@TrillCyborg
Created November 14, 2019 21:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TrillCyborg/7ce2ba18e89176cbff02f1b86a262ca1 to your computer and use it in GitHub Desktop.
Save TrillCyborg/7ce2ba18e89176cbff02f1b86a262ca1 to your computer and use it in GitHub Desktop.
Next.js / Accounts.js
import fetch from 'isomorphic-unfetch'
import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link'
import { createHttpLink } from 'apollo-link-http'
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory'
import { AccountsGraphQLClient } from '@accounts/graphql-client'
import { AccountsClientPassword } from '@accounts/client-password'
import { AccountsClient } from '@accounts/client'
import { accountsLink } from '@accounts/apollo-link'
import { tokenStorage } from './token-storage'
let apolloClient: {
client: ApolloClient<NormalizedCacheObject>
accountsGraphQL: AccountsGraphQLClient
accountsPassword: AccountsClientPassword
} = null
function create(opts?: { ctx?: any; initialState?: any }) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const isBrowser = typeof window !== 'undefined'
const cache = new InMemoryCache().restore(opts.initialState || {})
const httpLink = createHttpLink({
uri: process.env.GRAPHQL_URL, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
// Use fetch() polyfill on the server
fetch: !isBrowser && fetch,
})
const graphQLApolloClient = new ApolloClient({
link: ApolloLink.from([httpLink]),
cache,
})
const accountsGraphQL = new AccountsGraphQLClient({
graphQLClient: graphQLApolloClient,
})
const accountsClient = new AccountsClient(
{
tokenStorage: tokenStorage(opts.ctx),
},
accountsGraphQL
)
const accountsPassword = new AccountsClientPassword(accountsClient)
// regular apollo client
const authLink = accountsLink(() => accountsClient)
const client = new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
link: ApolloLink.from([authLink, httpLink]),
cache,
})
return { client, accountsGraphQL, accountsPassword, accountsClient }
}
export function initApollo(opts?: { ctx?: any; initialState?: any }) {
// 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(opts)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(opts)
}
return apolloClient
}
import cookies from 'next-cookies'
import Cookies from 'js-cookie'
export const tokenStorage = (ctx: any) => ({
setItem: async (key: string, value: string) => {
Cookies.set(key, value)
},
getItem: async (key: string) => {
const allCookies = cookies(ctx)
const item = allCookies[escape(key)] || allCookies[key]
return item
},
removeItem: async (key: string) => {
Cookies.remove(key)
},
getItemClientSync: (key: string) => {
return Cookies.get(key)
},
})
import React, { createContext, useMemo } from 'react'
import Head from 'next/head'
import { ApolloClient } from 'apollo-client'
import { ApolloProvider } from '@apollo/react-hooks'
import { NormalizedCacheObject } from 'apollo-cache-inmemory'
import { AccountsGraphQLClient } from '@accounts/graphql-client'
import { AccountsClientPassword } from '@accounts/client-password'
import { AccountsClient } from '@accounts/client'
import { initApollo } from './apollo'
export const ApolloContext = createContext<{
client: ApolloClient<NormalizedCacheObject>
accountsGraphQL: AccountsGraphQLClient
accountsPassword: AccountsClientPassword
accountsClient: AccountsClient
}>({
client: null,
accountsGraphQL: null,
accountsPassword: null,
accountsClient: 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 }) => {
const { client, accountsGraphQL, accountsPassword, accountsClient } = useMemo(
() => apolloClient || initApollo({ initialState: apolloState }),
[]
)
return (
<ApolloProvider client={client}>
<ApolloContext.Provider
value={{ accountsGraphQL, accountsPassword, accountsClient, client }}
>
<PageComponent
{...pageProps}
accountsGraphQL={accountsGraphQL as AccountsGraphQLClient}
accountsPassword={accountsPassword as AccountsClientPassword}
accountsClient={accountsClient as AccountsClient}
/>
</ApolloContext.Provider>
</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})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApollo({ ctx }))
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr')
await 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.client.cache.extract()
return {
...pageProps,
apolloState,
}
}
}
return WithApollo
}
export default withApollo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment