Next.js: withApollo Typescript

Created on 26 Nov 2019  路  24Comments  路  Source: vercel/next.js

Would you have a Typescript version of the with-apollo example?

Thanks!

good first issue example

Most helpful comment

I modify it a little bit to include the missing types which are:

interface NextPageContextWithApollo extends NextPageContext {
  apolloClient: ApolloClient<NormalizedCacheObject> | null;
  apolloState: NormalizedCacheObject;
  ctx: NextPageContextApp;
}

type NextPageContextApp = NextPageContextWithApollo & AppContext;

This is the complete code:

import React, { ReactNode } from 'react';
import App, { AppContext } from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { NextPageContext, NextPage } from 'next';
import createApolloClient from './apolloClient';

interface NextPageContextWithApollo extends NextPageContext {
  apolloClient: ApolloClient<NormalizedCacheObject> | null;
  apolloState: NormalizedCacheObject;
  ctx: NextPageContextApp;
}

type NextPageContextApp = NextPageContextWithApollo & AppContext;

// 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> | null = null;

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {NormalizedCacheObject} initialState
 * @param  {NextPageContext} ctx
 */
const initApolloClient = (initialState: NormalizedCacheObject, ctx?: NextPageContext) => {
  // 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, ctx);
  }

  // Reuse client on the client-side
  if (!globalApolloClient) {
    globalApolloClient = createApolloClient(initialState, ctx);
  }

  return globalApolloClient;
};

/**
 * Installs the Apollo Client on NextPageContext
 * or NextAppContext. Useful if you want to use apolloClient
 * inside getStaticProps, getStaticPaths or getServerSideProps
 * @param {NextPageContext | NextAppContext} ctx
 */
export const initOnContext = (ctx: NextPageContextApp): NextPageContextApp => {
  const inAppContext = Boolean(ctx.ctx);

  // We consider installing `withApollo({ ssr: true })` on global App level
  // as antipattern since it disables project wide Automatic Static Optimization.
  if (process.env.NODE_ENV === 'development') {
    if (inAppContext) {
      console.warn(
        'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
          'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n',
      );
    }
  }

  // Initialize ApolloClient if not already done
  // TODO: Add proper types here:
  // https://github.com/zeit/next.js/issues/9542
  const apolloClient = ctx.apolloClient || initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);

  // 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.
  // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
  // @ts-ignore
  apolloClient.toJSON = () => null;

  // Add apolloClient to NextPageContext & NextAppContext.
  // This allows us to consume the apolloClient inside our
  // custom `getInitialProps({ apolloClient })`.
  ctx.apolloClient = apolloClient;
  if (inAppContext) {
    ctx.ctx.apolloClient = apolloClient;
  }

  return ctx;
};

/**
 * Creates a withApollo HOC
 * that provides the apolloContext
 * to a next.js Page or AppTree.
 * @param  {Object} withApolloOptions
 * @param  {Boolean} [withApolloOptions.ssr=false]
 * @returns {(PageComponent: ReactNode) => ReactNode}
 */
export const withApollo = ({ ssr = false } = {}) => (PageComponent: NextPage): ReactNode => {
  const WithApollo = ({
    apolloClient,
    apolloState,
    ...pageProps
  }: {
    apolloClient: ApolloClient<NormalizedCacheObject>;
    apolloState: NormalizedCacheObject;
  }): ReactNode => {
    let client;
    if (apolloClient) {
      // Happens on: getDataFromTree & next.js ssr
      client = apolloClient;
    } else {
      // Happens on: next.js csr
      client = initApolloClient(apolloState, undefined);
    }

    return (
      <ApolloProvider client={client}>
        {/* eslint-disable-next-line react/jsx-props-no-spreading */}
        <PageComponent {...pageProps} />
      </ApolloProvider>
    );
  };

  // Set the correct displayName in development
  if (process.env.NODE_ENV !== 'production') {
    const displayName = PageComponent.displayName || PageComponent.name || 'Component';
    WithApollo.displayName = `withApollo(${displayName})`;
  }

  if (ssr || PageComponent.getInitialProps) {
    WithApollo.getInitialProps = async (ctx: NextPageContextApp): Promise<object> => {
      const inAppContext = Boolean(ctx.ctx);
      const { apolloClient } = initOnContext(ctx);

      // Run wrapped getInitialProps methods
      let pageProps = {};
      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx);
      } else if (inAppContext) {
        pageProps = await App.getInitialProps(ctx);
      }

      // Only on the server:
      if (typeof window === 'undefined') {
        const { AppTree } = ctx;
        // When redirecting, the response is finished.
        // No point in continuing to render
        if (ctx.res && ctx.res.finished) {
          return pageProps;
        }

        // Only if dataFromTree is enabled
        if (ssr && AppTree) {
          try {
            // Import `@apollo/react-ssr` dynamically.
            // We don't want to have this in our client bundle.
            const { getDataFromTree } = await import('@apollo/react-ssr');

            // Since AppComponents and PageComponents have different context types
            // we need to modify their props a little.
            let props: any;
            if (inAppContext) {
              props = { ...pageProps, apolloClient };
            } else {
              props = { pageProps: { ...pageProps, apolloClient } };
            }

            // Take the Next.js AppTree, determine which queries are needed to render,
            // and fetch them. This method can be pretty slow since it renders
            // your entire AppTree once for every query. Check out apollo fragments
            // if you want to reduce the number of rerenders.
            // https://www.apollographql.com/docs/react/data/fragments/
            // eslint-disable-next-line react/jsx-props-no-spreading
            await getDataFromTree(<AppTree {...props} />);
          } 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();
        }
      }

      return {
        ...pageProps,
        // Extract query data from the Apollo store
        apolloState: apolloClient?.cache.extract(),
        // Provide the client for ssr. As soon as this payload
        apolloClient: ctx.apolloClient,
      };
    };
  }

  return WithApollo;
};

All 24 comments

General curiosity, why did you ignore the issue template.

Hi @timneutkens sorry I didn't find one that would match this kinda of question, I was going to choose the Feature Request but this is not a feature whatsoever.

I apologize.

This would be great! I have converted lib/apollo.js in the past but always get stuck on the error where <AppTree pageProps={{...pageProps, apolloClient}} /> complains about apolloClient not being a valid prop of AppTree. I really hate having to @ts-ignore that line!

Additionally, there are a few parts of the existing JavaScript code where TypeScript exposes some issues with function arguments. If I have time, I will put up a CodeSandbox with what I was able to accomplish previously. It's like 99% of the way there.

@msheakoski That would be great, thanks!

@samuelcastro I did some workaround about this, you can try to find some inspiration in my template (newest versions on refactor branch (still working on), and previous one (which used to wrap _app.tsx) is on masterbranch.

Thanks @borisowsky it looks great.

Here's what I'm using copied as is from one of my projects:

// https://github.com/zeit/next.js/tree/ff2d28c4ff0a2cc1ce408817f2714eebf1cf72c2/examples/with-apollo

import React from 'react';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
import { fetch } from './fetch-polyfill';
import { NextPage } from 'next';

let apolloClient: ApolloClient<NormalizedCacheObject> | null = null;

/**
 * Creates and provides the apolloContext
 * to a next.js PageTree. Use it by wrapping
 * your PageComponent via HOC pattern.
 */
export function withApollo<PageProps>(PageComponent: NextPage<PageProps>, { ssr = true } = {}) {
  type ApolloPageProps = PageProps & { apolloClient?: ApolloClient<NormalizedCacheObject> | null; apolloState?: NormalizedCacheObject; }
  const WithApollo: NextPage<ApolloPageProps> = ({ apolloClient, apolloState, ...pageProps }) => {
    const client = apolloClient || initApolloClient(apolloState);
    return (
      <ApolloProvider client={client}>
        <PageComponent {...pageProps as any 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 cookie = ctx && ctx.req && ctx.req.headers && ctx.req.headers.cookie && String(ctx.req.headers.cookie);
      const apolloClient = (ctx.apolloClient = initApolloClient({}, cookie));

      // Run wrapped getInitialProps methods
      let pageProps = {} as 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.cache.extract();

      return {
        ...pageProps,
        apolloState,
      };
    };
  }

  return WithApollo;
}

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 */
function initApolloClient(initialState?: NormalizedCacheObject, cookie?: string) {
  // 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, cookie);
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = createApolloClient(initialState);
  }

  return apolloClient;
}

/**
 * Creates and configures the ApolloClient
 */
function createApolloClient(initialState: NormalizedCacheObject = {}, cookie?: string): ApolloClient<NormalizedCacheObject> {
  const headers = cookie ? {cookie}: undefined;
  const link = createPersistedQueryLink().concat(new HttpLink({
    uri: process.env.API_URL + '/graphql', // Server URL (must be absolute)
    credentials: 'include', // Additional fetch() options like `credentials` or `headers`
    headers,
    fetch,
    useGETForQueries: true,
  }));
  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  return new ApolloClient({
    ssrMode: typeof window === 'undefined', // Disables forceFetch on the server (so queries are only run once)
    link,
    cache: new InMemoryCache().restore(initialState),
    connectToDevTools: true
  });
}

Is anyone having issues with the example? i'm using it with typescript, similar to above and the data isnt being server rendered

@JClackett Sorry I thought I replied a couple of weeks ago when I last had a look at this. I too am having issues with this TypeScript Apollo example.
Using @mmiszy 's example was a great start but doesn't seem to work for me. Specifically when I tried to type withApollo<PageProps>.

Here's what I have so far with the current canary build of with-apollo project.

import React, { ReactNode } from 'react';
import App from 'next/app';
import { ApolloProvider } from '@apollo/react-hooks';
import createApolloClient from './createApolloClient';
import { NextPageContext, NextPage } from 'next';
// import { AppContext } from 'next/app';
import { ApolloClient, NormalizedCacheObject } from 'apollo-boost';

// 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> | null = null;

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {NormalizedCacheObject} initialState
 * @param  {NextPageContext} ctx
 */
const initApolloClient = (
  initialState: NormalizedCacheObject,
  ctx?: NextPageContext,
): ApolloClient<NormalizedCacheObject> => {
  // 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, ctx);
  }

  // Reuse client on the client-side
  if (!globalApolloClient) {
    globalApolloClient = createApolloClient(initialState, ctx);
  }

  return globalApolloClient;
};

/**
 * Installs the Apollo Client on NextPageContext
 * or NextAppContext. Useful if you want to use apolloClient
 * inside getStaticProps, getStaticPaths or getServerProps
 * @param {NextPageContext | NextAppContext} ctx
 */
export const initOnContext = (ctx: NextPageContext): NextPageContext => {
  const inAppContext = Boolean(ctx.ctx);

  // We consider installing `withApollo({ ssr: true })` on global App level
  // as antipattern since it disables project wide Automatic Static Optimization.
  if (process.env.NODE_ENV === 'development') {
    if (inAppContext) {
      console.warn(
        'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
          'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n',
      );
    }
  }

  // Initialize ApolloClient if not already done
  const apolloClient = ctx.apolloClient || initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);

  // 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.toJSON = (): null => null;

  // Add apolloClient to NextPageContext & NextAppContext.
  // This allows us to consume the apolloClient inside our
  // custom `getInitialProps({ apolloClient })`.
  ctx.apolloClient = apolloClient;
  if (inAppContext) {
    ctx.ctx.apolloClient = apolloClient;
  }

  return ctx;
};

/**
 * Creates a withApollo HOC
 * that provides the apolloContext
 * to a next.js Page or AppTree.
 * @param  {Object} withApolloOptions
 * @param  {Boolean} [withApolloOptions.ssr=false]
 * @returns {(PageComponent: ReactNode) => ReactNode}
 */
export function withApollo({ ssr = false } = {}) {
  return function(PageComponent: NextPage): ReactNode {
    function WithApollo({
      apolloClient,
      apolloState,
      ...pageProps
    }: {
      apolloClient: ApolloClient<NormalizedCacheObject>;
      apolloState: NormalizedCacheObject;
    }): ReactNode {
      let client;
      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} />
        </ApolloProvider>
      );
    }

    // Set the correct displayName in development
    if (process.env.NODE_ENV !== 'production') {
      const displayName = PageComponent.displayName || PageComponent.name || 'Component';
      WithApollo.displayName = `withApollo(${displayName})`;
    }

    if (ssr || PageComponent.getInitialProps) {
      WithApollo.getInitialProps = async (ctx: NextPageContext): Promise<object> => {
        const inAppContext = Boolean(ctx.ctx);
        const { apolloClient } = initOnContext(ctx);

        // Run wrapped getInitialProps methods
        let pageProps = {};
        if (PageComponent.getInitialProps) {
          pageProps = await PageComponent.getInitialProps(ctx);
        } else if (inAppContext) {
          pageProps = await App.getInitialProps(ctx);
        }

        // Only on the server:
        if (typeof window === 'undefined') {
          const { AppTree } = ctx;
          // When redirecting, the response is finished.
          // No point in continuing to render
          if (ctx.res && ctx.res.finished) {
            return pageProps;
          }

          // Only if dataFromTree is enabled
          if (ssr && AppTree) {
            try {
              // Import `@apollo/react-ssr` dynamically.
              // We don't want to have this in our client bundle.
              const { getDataFromTree } = await import('@apollo/react-ssr');

              // Since AppComponents and PageComponents have different context types
              // we need to modify their props a little.
              let props;
              if (inAppContext) {
                props = { ...pageProps, apolloClient };
              } else {
                props = { pageProps: { ...pageProps, apolloClient } };
              }

              // Take the Next.js AppTree, determine which queries are needed to render,
              // and fetch them. This method can be pretty slow since it renders
              // your entire AppTree once for every query. Check out apollo fragments
              // if you want to reduce the number of rerenders.
              // https://www.apollographql.com/docs/react/data/fragments/
              await getDataFromTree(<AppTree {...props} />);
            } 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);
            }
          }
        }

        return {
          ...pageProps,
          // Extract query data from the Apollo store
          apolloState: apolloClient.cache.extract(),
          // Provide the client for ssr. As soon as this payload
          // gets JSON.stringified it will remove itself.
          apolloClient: ctx.apolloClient,
        };
      };
    }

    return WithApollo;
  };
}

My outstanding issues are:

  • Property 'ctx' does not exist on type 'NextPageContext'.
  • Property 'apolloClient' does not exist on type 'NextPageContext'.
  • Property 'apolloState' does not exist on type 'NextPageContext'.
  • Type '{ apolloClient: any; } | { pageProps: { apolloClient: any; }; }' is not assignable to type 'IntrinsicAttributes & AppInitialProps & { [name: string]: any; } & { children?: ReactNode; }'. Property 'pageProps' is missing in type '{ apolloClient: any; }' but required in type 'AppInitialProps'. for 'AppTree'

I am not an expert on TypeScript so maybe these will make more sense to someone more experienced.
I see someone has added types for 'initOnContext' function but I can't find a reference for the NextAppContext type. Using 'AppContext' from 'next/app' was unsuccessful.

Any help would e greatly appreciated. Thanks

This is great thanks for sharing @jacknevitt! Maybe you should create a PR, I think this is a really good start.

I modify it a little bit to include the missing types which are:

interface NextPageContextWithApollo extends NextPageContext {
  apolloClient: ApolloClient<NormalizedCacheObject> | null;
  apolloState: NormalizedCacheObject;
  ctx: NextPageContextApp;
}

type NextPageContextApp = NextPageContextWithApollo & AppContext;

This is the complete code:

import React, { ReactNode } from 'react';
import App, { AppContext } from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { NextPageContext, NextPage } from 'next';
import createApolloClient from './apolloClient';

interface NextPageContextWithApollo extends NextPageContext {
  apolloClient: ApolloClient<NormalizedCacheObject> | null;
  apolloState: NormalizedCacheObject;
  ctx: NextPageContextApp;
}

type NextPageContextApp = NextPageContextWithApollo & AppContext;

// 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> | null = null;

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {NormalizedCacheObject} initialState
 * @param  {NextPageContext} ctx
 */
const initApolloClient = (initialState: NormalizedCacheObject, ctx?: NextPageContext) => {
  // 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, ctx);
  }

  // Reuse client on the client-side
  if (!globalApolloClient) {
    globalApolloClient = createApolloClient(initialState, ctx);
  }

  return globalApolloClient;
};

/**
 * Installs the Apollo Client on NextPageContext
 * or NextAppContext. Useful if you want to use apolloClient
 * inside getStaticProps, getStaticPaths or getServerSideProps
 * @param {NextPageContext | NextAppContext} ctx
 */
export const initOnContext = (ctx: NextPageContextApp): NextPageContextApp => {
  const inAppContext = Boolean(ctx.ctx);

  // We consider installing `withApollo({ ssr: true })` on global App level
  // as antipattern since it disables project wide Automatic Static Optimization.
  if (process.env.NODE_ENV === 'development') {
    if (inAppContext) {
      console.warn(
        'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
          'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n',
      );
    }
  }

  // Initialize ApolloClient if not already done
  // TODO: Add proper types here:
  // https://github.com/zeit/next.js/issues/9542
  const apolloClient = ctx.apolloClient || initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);

  // 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.
  // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
  // @ts-ignore
  apolloClient.toJSON = () => null;

  // Add apolloClient to NextPageContext & NextAppContext.
  // This allows us to consume the apolloClient inside our
  // custom `getInitialProps({ apolloClient })`.
  ctx.apolloClient = apolloClient;
  if (inAppContext) {
    ctx.ctx.apolloClient = apolloClient;
  }

  return ctx;
};

/**
 * Creates a withApollo HOC
 * that provides the apolloContext
 * to a next.js Page or AppTree.
 * @param  {Object} withApolloOptions
 * @param  {Boolean} [withApolloOptions.ssr=false]
 * @returns {(PageComponent: ReactNode) => ReactNode}
 */
export const withApollo = ({ ssr = false } = {}) => (PageComponent: NextPage): ReactNode => {
  const WithApollo = ({
    apolloClient,
    apolloState,
    ...pageProps
  }: {
    apolloClient: ApolloClient<NormalizedCacheObject>;
    apolloState: NormalizedCacheObject;
  }): ReactNode => {
    let client;
    if (apolloClient) {
      // Happens on: getDataFromTree & next.js ssr
      client = apolloClient;
    } else {
      // Happens on: next.js csr
      client = initApolloClient(apolloState, undefined);
    }

    return (
      <ApolloProvider client={client}>
        {/* eslint-disable-next-line react/jsx-props-no-spreading */}
        <PageComponent {...pageProps} />
      </ApolloProvider>
    );
  };

  // Set the correct displayName in development
  if (process.env.NODE_ENV !== 'production') {
    const displayName = PageComponent.displayName || PageComponent.name || 'Component';
    WithApollo.displayName = `withApollo(${displayName})`;
  }

  if (ssr || PageComponent.getInitialProps) {
    WithApollo.getInitialProps = async (ctx: NextPageContextApp): Promise<object> => {
      const inAppContext = Boolean(ctx.ctx);
      const { apolloClient } = initOnContext(ctx);

      // Run wrapped getInitialProps methods
      let pageProps = {};
      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx);
      } else if (inAppContext) {
        pageProps = await App.getInitialProps(ctx);
      }

      // Only on the server:
      if (typeof window === 'undefined') {
        const { AppTree } = ctx;
        // When redirecting, the response is finished.
        // No point in continuing to render
        if (ctx.res && ctx.res.finished) {
          return pageProps;
        }

        // Only if dataFromTree is enabled
        if (ssr && AppTree) {
          try {
            // Import `@apollo/react-ssr` dynamically.
            // We don't want to have this in our client bundle.
            const { getDataFromTree } = await import('@apollo/react-ssr');

            // Since AppComponents and PageComponents have different context types
            // we need to modify their props a little.
            let props: any;
            if (inAppContext) {
              props = { ...pageProps, apolloClient };
            } else {
              props = { pageProps: { ...pageProps, apolloClient } };
            }

            // Take the Next.js AppTree, determine which queries are needed to render,
            // and fetch them. This method can be pretty slow since it renders
            // your entire AppTree once for every query. Check out apollo fragments
            // if you want to reduce the number of rerenders.
            // https://www.apollographql.com/docs/react/data/fragments/
            // eslint-disable-next-line react/jsx-props-no-spreading
            await getDataFromTree(<AppTree {...props} />);
          } 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();
        }
      }

      return {
        ...pageProps,
        // Extract query data from the Apollo store
        apolloState: apolloClient?.cache.extract(),
        // Provide the client for ssr. As soon as this payload
        apolloClient: ctx.apolloClient,
      };
    };
  }

  return WithApollo;
};

Thanks for this @samuelcastro .
I've noticed you used // @ts-ignore above ApolloClient.toJSON(). Is it possible to type this? My setup prevents me from using ts-ignore as it suppresses compilation errors.
Also you have declared props for AppTree as any which is also not ideal.

Yeah I know, but that's all I could make, those are the 2 missing pieces I don't know yet how to type.

Thanks @jacknevitt , hope someone can help with the apolloClient.toJSON part. I still have problem with it.

Thanks @jacknevitt and @samuelcastro , btw i got error for using withApollo hoc in custom app _app.tsx

const MyApp: ({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element
Argument of type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to parameter of type 'NextComponentType<NextPageContext, {}, {}>'.
  Type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to type 'FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<{}>; }'.
    Type '({ Component, pageProps }: AppPropsType<Router, {}>) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.
      Types of parameters '__0' and 'props' are incompatible.
        Type '{ children?: ReactNode; }' is not assignable to type 'AppPropsType<Router, {}>'.
          Property 'pageProps' is missing in type '{ children?: ReactNode; }' but required in type 'AppInitialProps'.ts(2345)
utils.d.ts(102, 5): 'pageProps' is declared here.

@timshingyu Please check this issue, especially this comment: https://github.com/zeit/next.js/issues/10453#issuecomment-583555371
There were some break changes, please make sure you change the way to use withApollo
I used recompose so it will look like this:

export default compose(
    withApollo({ ssr: true }),
    withAuth(),
)(MyComponent);

@sangdth Thx, actually already using new syntax. Still got the type error.

Hi, I do not understand how I can access the client in getStaticPaths.

@mstn did you find a solution?

Hello, a solution I may use for the .toJSON type to avoid disable ts or use any should look like this :

  const withToJSONApolloClient =
    apolloClient as ApolloClient<NormalizedCacheObject> & { toJSON: () => null }

  withToJSONApolloClient.toJSON = () => null

Any idea how I can improve this ? 馃槂

You can use the same initOnContext function to access the client in GSSP/GSP.

What I am noticing is that there is no ctx.res unless it is a page component using this HOC in reference to this check. I'm using it with the App and there ctx.ctx.res exists. Perhaps this should check for whether or not inAppContext exists?

I tried @samuelcastro's solution but I keep getting this error Module not found: Can't resolve 'aws-sdk' in '[...]/node_modules/node-pre-gyp/lib'. I'm guessing some server-side dependencies somehow leak to the browser. It also complained that I cannot use getServerSideProps in conjunction with getInitialProps. I'm guessing this has to do with wrapping the getInitialProps methods after the if (ssr || PageComponent.getInitialProps) condition. Does anyone have an up-to-date fully working example? 馃檹

import * as React from 'react';
import { withApollo, WithApolloClient } from 'react-apollo';

class AComponent extends React.Component<WithApolloClient<{}>> {
  render() {
    // ...
  }
}

export default withApollo(AComponent);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

timneutkens picture timneutkens  路  3Comments

olifante picture olifante  路  3Comments

swrdfish picture swrdfish  路  3Comments

YarivGilad picture YarivGilad  路  3Comments

flybayer picture flybayer  路  3Comments