import { getDataFromTree } from '@apollo/react-ssr'; import ApolloClient from 'apollo-client'; import { AppProps, default as NextApp } from 'next/app'; import Head from 'next/head'; import PropTypes from 'prop-types'; import React from 'react'; import initApollo from './apollo'; import { ApolloContext, InitApolloClient, WithApolloOptions, WithApolloProps, WithApolloState } from './types'; const ssrMode = typeof window === 'undefined'; // Gets the display name of a JSX component for dev tools function getDisplayName(Component: React.ComponentType) { return Component.displayName || Component.name || 'Unknown'; } export default function withApollo( client: InitApolloClient, options: WithApolloOptions = {} ) { type ApolloProps = WithApolloProps & AppProps; if (!options.getDataFromTree) { options.getDataFromTree = 'always'; } return (App: typeof NextApp) => { return class WithApollo extends React.Component { public static displayName = `WithApollo(${getDisplayName(App)})`; public static propTypes = { apolloState: PropTypes.object, apollo: PropTypes.object }; public static getInitialProps = async (appCtx: ApolloContext) => { const { Component, router, ctx } = appCtx; const headers = ctx.req ? ctx.req.headers : {}; const apollo = initApollo(client, { ctx, headers }); const apolloState: WithApolloState = {}; const getInitialProps = App.getInitialProps; let appProps = { pageProps: {} }; if (getInitialProps) { ctx.apolloClient = apollo; appProps = await getInitialProps(appCtx); } if (ctx.res && (ctx.res.headersSent || ctx.res.finished)) { return {}; } if ( options.getDataFromTree === 'always' || (options.getDataFromTree === 'ssr' && ssrMode) ) { try { await getDataFromTree( ); } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. if (process.env.NODE_ENV !== 'production') { // tslint:disable-next-line no-console This is a necessary debugging log console.error('GraphQL error occurred [getDataFromTree]', error); } } if (ssrMode) { // getDataFromTree does not call componentWillUnmount // head side effect therefore need to be cleared manually Head.rewind(); } apolloState.data = apollo.cache.extract(); } return { ...appProps, apolloState }; }; public apollo: ApolloClient; constructor(props: ApolloProps) { super(props); this.apollo = props.apollo || initApollo(client, { initialState: props.apolloState.data }); } public render() { return ; } }; }; }