Skip to content

Instantly share code, notes, and snippets.

@tbantle22
Last active April 27, 2021 02:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbantle22/ec75d042bfa8e08818d0616c482e282f to your computer and use it in GitHub Desktop.
Save tbantle22/ec75d042bfa8e08818d0616c482e282f to your computer and use it in GitHub Desktop.
import { FeatureProvider } from "@dolthub/shared-components";
import App from "next/app";
import Head from "next/head";
import React, { ReactNode } from "react";
import { SWRConfig } from "swr";
import {
ServerConfigProvider,
useServerConfig,
} from "../contexts/serverConfig";
import { withApollo } from "../lib/apollo";
// configure fetch for use with SWR
const fetcher = async (input: RequestInfo, init: RequestInit) => {
const res = await fetch(input, init);
if (!res.ok) {
throw await res.json();
}
return res.json();
};
type Props = {
children: ReactNode;
};
export default class DoltHub extends App {
public render() {
const { Component, pageProps, router } = this.props;
// this.props.pageProps are the initial props fetched on a server side render.
// The following keeps the pageProps updated with the client navigation.
// This is necessary for pages that call getServerSideProps AND are routed to
// from within the app (i.e. router.push()).
pageProps.params = {
...router.query,
};
const WrappedPage = withApollo()(Component);
return (
<>
<Head>
{/* Add script and link tags here */}
</Head>
<SWRConfig value={{ fetcher }}>
<ServerConfigProvider>
<WrappedPage {...pageProps} />
</ServerConfigProvider>
</SWRConfig>
</>
);
}
}
// lib/apollo.tsx
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
} from "@apollo/client";
import { NormalizedCacheObject } from "@apollo/client/cache";
import { IncomingMessage } from "http";
import fetch from "isomorphic-unfetch";
import { NextPage, NextPageContext } from "next";
import getConfig from "next/config";
import React from "react";
import { possibleTypes } from "../gen/fragmentTypes.json";
const {
publicRuntimeConfig: { graphqlApiUrl },
} = getConfig();
export function createApolloClient(
uri: string,
initialState?: NormalizedCacheObject,
req?: IncomingMessage,
) {
// Add headers here
const headers = {};
const cache = new InMemoryCache({
possibleTypes,
}).restore(initialState || {});
return new ApolloClient({
cache,
link: new HttpLink({
fetch,
credentials: "include",
uri,
headers,
}),
});
}
// On the client, we store the Apollo Client in the following variable.
// This prevents the client from reinitializing between page transitions.
let globalApolloClient: ApolloClient<NormalizedCacheObject> | undefined;
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {NormalizedCacheObject} initialState
* @param {IncomingMessage} req
*/
export const initApolloClient = (
initialState?: NormalizedCacheObject,
req?: IncomingMessage,
) => {
// 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 createApolloClient(
process.env.INTERNAL_GRAPHQLAPI_URL!,
initialState,
req,
);
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(graphqlApiUrl, initialState, req);
}
return globalApolloClient;
};
type ApolloContext = {
apolloClient?: ApolloClient<NormalizedCacheObject>;
apolloState?: NormalizedCacheObject;
};
type ContextWithClient = NextPageContext & ApolloContext;
/**
* Installs the Apollo Client on NextPageContext
* Useful if you want to use apolloClient
* inside getStaticProps, getStaticPaths or getServerSideProps
* @param {NextPageContext | NextAppContext} ctx
*/
export const initOnContext = (ctx: NextPageContext) => {
const newCtx: ContextWithClient = ctx;
// Initialize ApolloClient if not already done
const apolloClient =
newCtx.apolloClient ?? initApolloClient(newCtx.apolloState ?? {}, ctx.req);
// We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
// Otherwise, the component would have to call initApollo() again but this
// time without the context. Once that happens, the following code will make sure we send
// the prop as `null` to the browser.
(apolloClient as any).toJSON = () => null;
// Add apolloClient to NextPageContext.
newCtx.apolloClient = apolloClient;
return newCtx;
};
/**
* Creates a withApollo HOC
* that provides the apolloContext
* to a next.js Page or AppTree.
* @returns {(PageComponent: ReactNode) => ReactNode}
*/
export function withApollo<
P extends Record<string, unknown> = Record<string, unknown>
>() {
return (PageComponent: NextPage<P>) => {
const WithApollo = ({
apolloClient,
apolloState,
...pageProps
}: ApolloContext) => {
let client: ApolloClient<NormalizedCacheObject>;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
client = initApolloClient(apolloState, undefined);
}
return (
<ApolloProvider client={client}>
<PageComponent {...(pageProps as P)} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
(PageComponent.displayName ?? PageComponent.name) || "Component";
WithApollo.displayName = `withApollo(${displayName})`;
}
return WithApollo;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment