diff --git a/app/providers/with-data.js b/app/providers/with-data.js index 0ab9b3b..286009c 100644 --- a/app/providers/with-data.js +++ b/app/providers/with-data.js @@ -1,120 +1,74 @@ import 'cross-fetch/polyfill' -import { Component } from 'react' +import React, { Component } from 'react' +import getDisplayName from 'react-display-name' import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' import { createUploadLink } from 'apollo-upload-client' import { getDataFromTree, ApolloProvider } from 'react-apollo' -import getDisplayName from 'react-display-name' -import Head from 'next/head' +import { Router } from 'next/router' +import App from 'next/app' -const ssrMode = !process.browser +// Shares the browser ApolloClient instance between pages. +let browserApolloClient -// To share an instance between pages on the client -let apolloClient - -/** - * Creates a new Apollo Client instance. - * @param {Object} [initialState] - Redux initial state. - * @returns {Object} Apollo Client instance. - */ -const createApolloClient = (initialState = {}) => +const createApolloClient = (cache = {}) => new ApolloClient({ - ssrMode, - cache: new InMemoryCache().restore(initialState), + ssrMode: typeof window !== 'undefined', + cache: new InMemoryCache().restore(cache), link: createUploadLink({ uri: process.env.API_URI }) }) -export default ComposedComponent => +export const withData = Composed => class WithData extends Component { - static displayName = `WithData(${getDisplayName(ComposedComponent)})` - - /** - * Gets the initial props for a Next.js page component. - * Executes on the server for the initial page load. Executes on the client - * when navigating to a different route via the Link component or using the - * routing APIs. For either environment the initial props returned must be - * serializable to JSON. - * @see https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle - * @param {Object} context - * @param {String} context.pathname - Page URL path. - * @param {String} context.asPath - Page URL path and query as shown in browser. - * @param {Object} context.query - Query string section of the page URL parsed as an object. - * @param {Object} context.req - HTTP request (server only). - * @param {Object} context.res - HTTP response (server only). - * @param {Object} context.jsonPageRes - Fetch Response (client only). - * @param {Object} context.err - Error encountered during the rendering, if any. - * @returns {Promise} Page component props. - */ - static async getInitialProps(context) { - const initialProps = { - composedComponentProps: { - router: { - pathname: context.pathname, - asPath: context.asPath, - query: context.query - } - } - } - - // If the page component has initial props, merge them in. - if (ComposedComponent.getInitialProps) - Object.assign( - initialProps.composedComponentProps, - await ComposedComponent.getInitialProps(context) - ) - - if (ssrMode) { - const apolloClient = createApolloClient() - - try { - // Recurse the component tree and prefetch all Apollo data queries to - // populate the Apollo Client Redux store. This allows an instant - // server side render. - // See: https://www.apollographql.com/docs/react/recipes/server-side-rendering.html#getDataFromTree - await getDataFromTree( - - - - ) - } catch (error) { - // Prevent Apollo Client GraphQL errors from crashing SSR. Handle them - // in components via the data.error prop. - // See: https://www.apollographql.com/docs/react/basics/queries.html#graphql-query-data-error - // eslint-disable-next-line no-console - console.error(error.message) - } - - // Forget Head items found during the getDataFromTree render to prevent - // duplicates in the real render. - Head.rewind() - - // Set Apollo Client initial state so the client can adopt data fetched - // on the server. - initialProps.initialState = apolloClient.cache.extract() - } - - // Return the final page component initial props - return initialProps - } - constructor(props) { super(props) - if (ssrMode) - // For the server an Apollo Client instance exists per request - this.apolloClient = createApolloClient(this.props.initialState) - else { - // For the client an Apollo Client instance is shared between pages - if (!apolloClient) - apolloClient = createApolloClient(this.props.initialState) - this.apolloClient = apolloClient - } + + // Set the ApolloClient instance used in render(). + this.apolloClient = + typeof window !== 'undefined' + ? // Client: Shared instance, created at first render after SSR. + (browserApolloClient = + browserApolloClient || createApolloClient(props.cache)) + : // Server: Private instance for SSR. + createApolloClient(props.cache) } + static displayName = `WithApollo(${getDisplayName(Composed)})` + + static async getInitialProps(ctx) { + // Use initial props from nested page decorators. + const props = Composed.getInitialProps + ? await Composed.getInitialProps(ctx) + : {} + + // If server environment, preload the page. + if (ctx.req) { + const apolloClient = createApolloClient() + + await getDataFromTree( + + ) + + props.cache = apolloClient.cache.extract() + } + + return props + } + + static renderPage = ({ apolloClient, pageProps }) => ( + + + + ) + render() { - return ( - - - - ) + return this.constructor.renderPage({ + apolloClient: this.apolloClient, + pageProps: this.props + }) } }