Skip to content

Instantly share code, notes, and snippets.

@d-dmytro
Last active February 15, 2022 11:13
Show Gist options
  • Save d-dmytro/5269b6cba5efea557ce35ad38778ec2e to your computer and use it in GitHub Desktop.
Save d-dmytro/5269b6cba5efea557ce35ad38778ec2e to your computer and use it in GitHub Desktop.
Types for the Apollo setup file from the Next's "with-apollo" example (https://github.com/zeit/next.js/tree/canary/examples/with-apollo). If you've got a suggestion how to improve this, please post it in the comments section.
import React from 'react';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import fetch from 'isomorphic-unfetch';
import { NextPage } from 'next';
export type AppApolloCache = any;
let apolloClient: ApolloClient<AppApolloCache> | null = null;
interface ApolloInitialProps {
apolloState?: AppApolloCache;
}
interface ApolloProps extends ApolloInitialProps {
apolloClient?: ApolloClient<any>;
}
/**
* 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<PageProps extends object, InitialProps = PageProps>(
PageComponent: NextPage<PageProps, InitialProps>,
{ ssr = true } = {}
) {
const WithApollo: NextPage<
ApolloProps & PageProps,
ApolloInitialProps & InitialProps
> = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...(pageProps as PageProps)} />
</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 = initApolloClient());
// Run wrapped getInitialProps methods
let pageProps = {} as InitialProps;
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.cache.extract();
return {
...pageProps,
apolloState
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState?: AppApolloCache) {
// 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(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState: AppApolloCache = {}) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient<AppApolloCache>({
ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
fetch
}),
cache: new InMemoryCache().restore(initialState)
});
}
@d-dmytro
Copy link
Author

d-dmytro commented Nov 7, 2019

Also, to be able to assign the Apollo client instance to the apolloClient property of the Next's page context object (ctx.apolloClient), we should add the following file to the app's root. This file modifies the NextPageContext type exported by the "next" module:

// next.d.ts
import ApolloClient from 'apollo-client';
import { AppApolloCache } from './lib/apollo';

declare module 'next' {
  export interface NextPageContext {
    apolloClient?: ApolloClient<AppApolloCache>;
  }
}

Copy link

ghost commented Jan 25, 2020

Hello Dmytro , I follow your tutorial but get this error when running with-apollo.tsx with latest version of the relevence dependencies , so I use the official typescript-with-apollo (client and server in the same project directory) and changed a bit to be as similar as /with-apollo (which only setup the client-only as per tutorial) repo-example and it WORKS, took me 1 and a half day to get this right.

@d-dmytro
Copy link
Author

Hi Syahmi,
Thanks for reporting this.
However, I couldn't reproduce this error. I've downloaded the source code from the lesson called "Adding Apollo" and upgraded Apollo 2.6 to Apollo 3 (https://www.npmjs.com/package/@apollo/client), but the error did not appear. This is the list of Apollo dependencies and their versions:

"@apollo/client": "^3.0.0-beta.25",
"@apollo/react-ssr": "^3.1.3",
"graphql": "^14.5.8"

@lakshyasharma14
Copy link

lakshyasharma14 commented Feb 15, 2022

Hi, I am using the same script with "@apollo/client": "^3.5.8" and using @apollo/client/react/ssr instead of @apollo/react-ssr . But somehow the after const { AppTree } = ctx; I console log the output of AppTree and I find no queries in it. So my I am not able to call the server-side. It is directly getting called from the client then because there was nothing in apolloState.

import { useQuery, gql } from '@apollo/client';
import { withApollo } from '../lib/apollo/withApollo';

const MyQuery = gql`
  query MyQuery {
    trip {
      id
    }
  }
`;
const Sample = () => {
  const { loading, error, data } = useQuery(MyQuery);

  if (loading) {
    return <div>Loading...</div>;
  }
  if (error) {
    console.error(error);
    return <div>Error!</div>;
  }

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};
export default withApollo({ ssr: true })(Sample);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment