Created
November 14, 2019 21:08
-
-
Save TrillCyborg/7ce2ba18e89176cbff02f1b86a262ca1 to your computer and use it in GitHub Desktop.
Revisions
-
TrillCyborg created this gist
Nov 14, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,67 @@ 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 } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,19 @@ 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) }, }) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,126 @@ 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