Better withData page decorator.
This commit is contained in:
parent
d3daccaab1
commit
1e40097480
@ -1,120 +1,74 @@
|
|||||||
import 'cross-fetch/polyfill'
|
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 { ApolloClient } from 'apollo-client'
|
||||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||||
import { createUploadLink } from 'apollo-upload-client'
|
import { createUploadLink } from 'apollo-upload-client'
|
||||||
import { getDataFromTree, ApolloProvider } from 'react-apollo'
|
import { getDataFromTree, ApolloProvider } from 'react-apollo'
|
||||||
import getDisplayName from 'react-display-name'
|
import { Router } from 'next/router'
|
||||||
import Head from 'next/head'
|
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
|
const createApolloClient = (cache = {}) =>
|
||||||
let apolloClient
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Apollo Client instance.
|
|
||||||
* @param {Object} [initialState] - Redux initial state.
|
|
||||||
* @returns {Object} Apollo Client instance.
|
|
||||||
*/
|
|
||||||
const createApolloClient = (initialState = {}) =>
|
|
||||||
new ApolloClient({
|
new ApolloClient({
|
||||||
ssrMode,
|
ssrMode: typeof window !== 'undefined',
|
||||||
cache: new InMemoryCache().restore(initialState),
|
cache: new InMemoryCache().restore(cache),
|
||||||
link: createUploadLink({ uri: process.env.API_URI })
|
link: createUploadLink({ uri: process.env.API_URI })
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ComposedComponent =>
|
export const withData = Composed =>
|
||||||
class WithData extends Component {
|
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(
|
|
||||||
<ApolloProvider client={apolloClient}>
|
|
||||||
<ComposedComponent {...initialProps.composedComponentProps} />
|
|
||||||
</ApolloProvider>
|
|
||||||
)
|
|
||||||
} 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) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
if (ssrMode)
|
|
||||||
// For the server an Apollo Client instance exists per request
|
// Set the ApolloClient instance used in render().
|
||||||
this.apolloClient = createApolloClient(this.props.initialState)
|
this.apolloClient =
|
||||||
else {
|
typeof window !== 'undefined'
|
||||||
// For the client an Apollo Client instance is shared between pages
|
? // Client: Shared instance, created at first render after SSR.
|
||||||
if (!apolloClient)
|
(browserApolloClient =
|
||||||
apolloClient = createApolloClient(this.props.initialState)
|
browserApolloClient || createApolloClient(props.cache))
|
||||||
this.apolloClient = apolloClient
|
: // 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(
|
||||||
|
<App
|
||||||
|
router={new Router(ctx.pathname, ctx.query, ctx.asPath)}
|
||||||
|
pageProps={{ apolloClient, pageProps: props }}
|
||||||
|
Component={this.renderPage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
props.cache = apolloClient.cache.extract()
|
||||||
|
}
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
static renderPage = ({ apolloClient, pageProps }) => (
|
||||||
|
<ApolloProvider client={apolloClient}>
|
||||||
|
<Composed {...pageProps} />
|
||||||
|
</ApolloProvider>
|
||||||
|
)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return this.constructor.renderPage({
|
||||||
<ApolloProvider client={this.apolloClient}>
|
apolloClient: this.apolloClient,
|
||||||
<ComposedComponent {...this.props.composedComponentProps} />
|
pageProps: this.props
|
||||||
</ApolloProvider>
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user