Apollo decorator refactor.

This commit is contained in:
Jayden Seric 2017-07-10 13:59:49 +10:00
parent 4b9f461c40
commit 8d3262500d
3 changed files with 121 additions and 57 deletions

View File

@ -0,0 +1,8 @@
/**
* Gets the display name of a JSX component for dev tools.
* @param {Object | Function} component - A JSX component.
* @returns {String} The component display name.
*/
export default function getDisplayName({ displayName, name }) {
return displayName ? displayName : name && name !== '' ? name : 'Unknown'
}

View File

@ -0,0 +1,37 @@
import { ApolloClient } from 'react-apollo'
import { createNetworkInterface } from 'apollo-upload-client'
import 'isomorphic-fetch'
// Used in the browser to share a single Apollo Client instance between
// decorated components.
let apolloClient = null
/**
* Creates a new Apollo Client instance.
* @param {Object} [initialState] - Apollo client Redux store initial state.
* @returns {Object} Apollo Client instance.
*/
const createApolloClient = initialState =>
new ApolloClient({
initialState,
ssrMode: !process.browser,
networkInterface: createNetworkInterface({
uri: process.env.API_URI
})
})
/**
* Gets or creates the Apollo Client instance.
* @param {Object} [initialState] - Apollo client Redux store initial state.
* @returns {Object} Apollo Client instance.
*/
export default function initApolloClient(initialState) {
// Create a new client every server-side request so that data isn't shared
// between connections.
if (!process.browser) return createApolloClient(initialState)
// Reuse client on the client-side.
if (!apolloClient) apolloClient = createApolloClient(initialState)
return apolloClient
}

View File

@ -1,73 +1,92 @@
import 'isomorphic-fetch'
import React from 'react'
import { ApolloClient, ApolloProvider, getDataFromTree } from 'react-apollo'
import { createNetworkInterface } from 'apollo-upload-client'
import { Component } from 'react'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApolloClient from './init-apollo-client'
import getDisplayName from './get-display-name'
const ssrMode = !process.browser
let apolloClient = null
export default ComposedComponent => {
return class WithData extends Component {
static displayName = `WithData(${getDisplayName(ComposedComponent)})`
function initClient(headers, initialState) {
return new ApolloClient({
initialState,
ssrMode,
networkInterface: createNetworkInterface({
uri: process.env.API_URI
})
})
}
function getClient(headers, initialState = {}) {
if (ssrMode) return initClient(headers, initialState)
if (!apolloClient) apolloClient = initClient(headers, initialState)
return apolloClient
}
export default Component =>
class extends React.Component {
static async getInitialProps(ctx) {
const headers = ctx.req ? ctx.req.headers : {}
const client = getClient(headers)
const props = {
url: {
query: ctx.query,
pathname: ctx.pathname
},
...(await (Component.getInitialProps
? Component.getInitialProps(ctx)
: {}))
}
if (ssrMode) {
const app = (
<ApolloProvider client={client}>
<Component {...props} />
</ApolloProvider>
)
await getDataFromTree(app)
}
return {
initialState: {
apollo: {
data: client.getInitialState().data
/**
* 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/issues/978
* @see https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle
* @param {Object} context
* @param {String} context.pathname - Path section of the page URL.
* @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: {
url: {
query: context.query,
pathname: context.pathname
}
},
headers,
...props
}
}
// If the page component has initial props, merge them in.
if (ComposedComponent.getInitialProps) {
Object.assign(
initialProps.composedComponentProps,
await ComposedComponent.getInitialProps(context)
)
}
if (!process.browser) {
const apolloClient = initApolloClient()
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: http://dev.apollodata.com/react/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:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
}
// 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 = {
apollo: apolloClient.getInitialState()
}
}
return initialProps
}
constructor(props) {
super(props)
this.client = getClient(this.props.headers, this.props.initialState)
this.apolloClient = initApolloClient(this.props.initialState)
}
render() {
return (
<ApolloProvider client={this.client}>
<Component {...this.props} />
<ApolloProvider client={this.apolloClient}>
<ComposedComponent {...this.props.composedComponentProps} />
</ApolloProvider>
)
}
}
}