diff --git a/examples/with-apollo-and-redux/lib/initApollo.js b/examples/with-apollo-and-redux/lib/initApollo.js new file mode 100644 index 00000000..d7f8c199 --- /dev/null +++ b/examples/with-apollo-and-redux/lib/initApollo.js @@ -0,0 +1,36 @@ +import { ApolloClient, createNetworkInterface } from 'react-apollo' +import fetch from 'isomorphic-fetch' + +let apolloClient = null + +// Polyfill fetch() on the server (used by apollo-client) +if (!process.browser) { + global.fetch = fetch +} + +function create () { + return new ApolloClient({ + ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once) + networkInterface: createNetworkInterface({ + uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute) + opts: { // Additional fetch() options like `credentials` or `headers` + credentials: 'same-origin' + } + }) + }) +} + +export default function initApollo () { + // 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 (!process.browser) { + return create() + } + + // Reuse client on the client-side + if (!apolloClient) { + apolloClient = create() + } + + return apolloClient +} diff --git a/examples/with-apollo-and-redux/lib/initClient.js b/examples/with-apollo-and-redux/lib/initClient.js deleted file mode 100644 index 31f590f3..00000000 --- a/examples/with-apollo-and-redux/lib/initClient.js +++ /dev/null @@ -1,28 +0,0 @@ -import { ApolloClient, createNetworkInterface } from 'react-apollo' - -let apolloClient = null - -function _initClient (headers, initialState) { - return new ApolloClient({ - initialState, - ssrMode: !process.browser, - dataIdFromObject: result => result.id || null, - networkInterface: createNetworkInterface({ - uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', - opts: { - credentials: 'same-origin' - // Pass headers here if your graphql server requires them - } - }) - }) -} - -export const initClient = (headers, initialState = {}) => { - if (!process.browser) { - return _initClient(headers, initialState) - } - if (!apolloClient) { - apolloClient = _initClient(headers, initialState) - } - return apolloClient -} diff --git a/examples/with-apollo-and-redux/lib/initRedux.js b/examples/with-apollo-and-redux/lib/initRedux.js new file mode 100644 index 00000000..28f880c7 --- /dev/null +++ b/examples/with-apollo-and-redux/lib/initRedux.js @@ -0,0 +1,39 @@ +import { createStore, combineReducers, applyMiddleware, compose } from 'redux' +import reducers from './reducers' + +let reduxStore = null + +// Get the Redux DevTools extension and fallback to a no-op function +let devtools = f => f +if (process.browser && window.__REDUX_DEVTOOLS_EXTENSION__) { + devtools = window.__REDUX_DEVTOOLS_EXTENSION__() +} + +function create (apollo, initialState = {}) { + return createStore( + combineReducers({ // Setup reducers + ...reducers, + apollo: apollo.reducer() + }), + initialState, // Hydrate the store with server-side data + compose( + applyMiddleware(apollo.middleware()), // Add additional middleware here + devtools + ) + ) +} + +export default function initRedux (apollo, initialState) { + // Make sure to create a new store for every server-side request so that data + // isn't shared between connections (which would be bad) + if (!process.browser) { + return create(apollo, initialState) + } + + // Reuse store on the client-side + if (!reduxStore) { + reduxStore = create(apollo, initialState) + } + + return reduxStore +} diff --git a/examples/with-apollo-and-redux/lib/initStore.js b/examples/with-apollo-and-redux/lib/initStore.js deleted file mode 100644 index 7c95b51f..00000000 --- a/examples/with-apollo-and-redux/lib/initStore.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createStore } from 'redux' -import getReducer from './reducer' -import createMiddleware from './middleware' - -let reduxStore = null - -export const initStore = (client, initialState) => { - let store - if (!process.browser || !reduxStore) { - const middleware = createMiddleware(client.middleware()) - store = createStore(getReducer(client), initialState, middleware) - if (!process.browser) { - return store - } - reduxStore = store - } - return reduxStore -} diff --git a/examples/with-apollo-and-redux/lib/middleware.js b/examples/with-apollo-and-redux/lib/middleware.js deleted file mode 100644 index 1ff86a58..00000000 --- a/examples/with-apollo-and-redux/lib/middleware.js +++ /dev/null @@ -1,9 +0,0 @@ -import { applyMiddleware, compose } from 'redux' - -export default function createMiddleware (clientMiddleware) { - const middleware = applyMiddleware(clientMiddleware) - if (process.browser && window.devToolsExtension) { - return compose(middleware, window.devToolsExtension()) - } - return middleware -} diff --git a/examples/with-apollo-and-redux/lib/reducer.js b/examples/with-apollo-and-redux/lib/reducer.js deleted file mode 100644 index 95434891..00000000 --- a/examples/with-apollo-and-redux/lib/reducer.js +++ /dev/null @@ -1,7 +0,0 @@ -import { combineReducers } from 'redux' - -export default function getReducer (client) { - return combineReducers({ - apollo: client.reducer() - }) -} diff --git a/examples/with-apollo-and-redux/lib/reducers.js b/examples/with-apollo-and-redux/lib/reducers.js new file mode 100644 index 00000000..e22542ed --- /dev/null +++ b/examples/with-apollo-and-redux/lib/reducers.js @@ -0,0 +1,12 @@ +export default { + example: (state = {}, { type, payload }) => { + switch (type) { + case 'EXAMPLE_ACTION': + return { + ...state + } + default: + return state + } + } +} diff --git a/examples/with-apollo-and-redux/lib/withData.js b/examples/with-apollo-and-redux/lib/withData.js index 206fdf80..1c08274b 100644 --- a/examples/with-apollo-and-redux/lib/withData.js +++ b/examples/with-apollo-and-redux/lib/withData.js @@ -1,56 +1,75 @@ -import 'isomorphic-fetch' import React from 'react' +import PropTypes from 'prop-types' import { ApolloProvider, getDataFromTree } from 'react-apollo' -import { initClient } from './initClient' -import { initStore } from './initStore' +import initApollo from './initApollo' +import initRedux from './initRedux' + +export default ComposedComponent => { + return class WithData extends React.Component { + static displayName = `WithData(${ComposedComponent.displayName})` + static propTypes = { + serverState: PropTypes.object.isRequired + } -export default (Component) => ( - class extends React.Component { static async getInitialProps (ctx) { - const headers = ctx.req ? ctx.req.headers : {} - const client = initClient(headers) - const store = initStore(client, client.initialState) + let serverState = {} - const props = { - url: { query: ctx.query, pathname: ctx.pathname }, - ...await (Component.getInitialProps ? Component.getInitialProps(ctx) : {}) + // Evaluate the composed component's getInitialProps() + let composedInitialProps = {} + if (ComposedComponent.getInitialProps) { + composedInitialProps = await ComposedComponent.getInitialProps(ctx) } + // Run all graphql queries in the component tree + // and extract the resulting data if (!process.browser) { + const apollo = initApollo() + const redux = initRedux(apollo) + // Provide the `url` prop data in case a graphql query uses it + const url = {query: ctx.query, pathname: ctx.pathname} + + // Run all graphql queries const app = ( - - + // No need to use the Redux Provider + // because Apollo sets up the store for us + + ) await getDataFromTree(app) + + // Extract query data from the store + const state = redux.getState() + + // No need to include other initial Redux state because when it + // initialises on the client-side it'll create it again anyway + serverState = { + apollo: { // Make sure to only include Apollo's data state + data: state.apollo.data + } + } } - const state = store.getState() - return { - initialState: { - ...state, - apollo: { - data: client.getInitialState().data - } - }, - headers, - ...props + serverState, + ...composedInitialProps } } constructor (props) { super(props) - this.client = initClient(this.props.headers, this.props.initialState) - this.store = initStore(this.client, this.props.initialState) + this.apollo = initApollo() + this.redux = initRedux(this.apollo, this.props.serverState) } render () { return ( - - + // No need to use the Redux Provider + // because Apollo sets up the store for us + + ) } } -) +} diff --git a/examples/with-apollo-and-redux/package.json b/examples/with-apollo-and-redux/package.json index 455d5a2e..899ffe62 100644 --- a/examples/with-apollo-and-redux/package.json +++ b/examples/with-apollo-and-redux/package.json @@ -1,16 +1,19 @@ { "name": "with-apollo-and-redux", - "version": "1.0.0", + "version": "2.0.0", "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "dependencies": { - "graphql": "^0.9.1", + "graphql": "^0.9.3", + "isomorphic-fetch": "^2.2.1", "next": "latest", - "react": "^15.4.2", - "react-apollo": "^1.0.0-rc.2", + "prop-types": "^15.5.8", + "react": "^15.5.4", + "react-apollo": "^1.1.3", + "react-dom": "^15.5.4", "redux": "^3.6.0" }, "author": "", diff --git a/examples/with-apollo/lib/initApollo.js b/examples/with-apollo/lib/initApollo.js new file mode 100644 index 00000000..dfd1976f --- /dev/null +++ b/examples/with-apollo/lib/initApollo.js @@ -0,0 +1,37 @@ +import { ApolloClient, createNetworkInterface } from 'react-apollo' +import fetch from 'isomorphic-fetch' + +let apolloClient = null + +// Polyfill fetch() on the server (used by apollo-client) +if (!process.browser) { + global.fetch = fetch +} + +function create (initialState) { + return new ApolloClient({ + initialState, + ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once) + networkInterface: createNetworkInterface({ + uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute) + opts: { // Additional fetch() options like `credentials` or `headers` + credentials: 'same-origin' + } + }) + }) +} + +export default function initApollo (initialState) { + // 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 (!process.browser) { + return create(initialState) + } + + // Reuse client on the client-side + if (!apolloClient) { + apolloClient = create(initialState) + } + + return apolloClient +} diff --git a/examples/with-apollo/lib/initClient.js b/examples/with-apollo/lib/initClient.js deleted file mode 100644 index 31f590f3..00000000 --- a/examples/with-apollo/lib/initClient.js +++ /dev/null @@ -1,28 +0,0 @@ -import { ApolloClient, createNetworkInterface } from 'react-apollo' - -let apolloClient = null - -function _initClient (headers, initialState) { - return new ApolloClient({ - initialState, - ssrMode: !process.browser, - dataIdFromObject: result => result.id || null, - networkInterface: createNetworkInterface({ - uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', - opts: { - credentials: 'same-origin' - // Pass headers here if your graphql server requires them - } - }) - }) -} - -export const initClient = (headers, initialState = {}) => { - if (!process.browser) { - return _initClient(headers, initialState) - } - if (!apolloClient) { - apolloClient = _initClient(headers, initialState) - } - return apolloClient -} diff --git a/examples/with-apollo/lib/withData.js b/examples/with-apollo/lib/withData.js index ee8ffd3e..faf8a018 100644 --- a/examples/with-apollo/lib/withData.js +++ b/examples/with-apollo/lib/withData.js @@ -1,50 +1,66 @@ -import 'isomorphic-fetch' import React from 'react' +import PropTypes from 'prop-types' import { ApolloProvider, getDataFromTree } from 'react-apollo' -import { initClient } from './initClient' +import initApollo from './initApollo' + +export default ComposedComponent => { + return class WithData extends React.Component { + static displayName = `WithData(${ComposedComponent.displayName})` + static propTypes = { + serverState: PropTypes.object.isRequired + } -export default (Component) => ( - class extends React.Component { static async getInitialProps (ctx) { - const headers = ctx.req ? ctx.req.headers : {} - const client = initClient(headers) + let serverState = {} - const props = { - url: { query: ctx.query, pathname: ctx.pathname }, - ...await (Component.getInitialProps ? Component.getInitialProps(ctx) : {}) + // Evaluate the composed component's getInitialProps() + let composedInitialProps = {} + if (ComposedComponent.getInitialProps) { + composedInitialProps = await ComposedComponent.getInitialProps(ctx) } + // Run all graphql queries in the component tree + // and extract the resulting data if (!process.browser) { + const apollo = initApollo() + // Provide the `url` prop data in case a graphql query uses it + const url = {query: ctx.query, pathname: ctx.pathname} + + // Run all graphql queries const app = ( - - + + ) await getDataFromTree(app) + + // Extract query data from the Apollo's store + const state = apollo.getInitialState() + + serverState = { + apollo: { // Make sure to only include Apollo's data state + data: state.data + } + } } return { - initialState: { - apollo: { - data: client.getInitialState().data - } - }, - headers, - ...props + serverState, + ...composedInitialProps } } constructor (props) { super(props) - this.client = initClient(this.props.headers, this.props.initialState) + this.apollo = initApollo(this.props.serverState) } render () { return ( - - + + ) } } -) +} diff --git a/examples/with-apollo/package.json b/examples/with-apollo/package.json index 778aef08..9d48d3a2 100644 --- a/examples/with-apollo/package.json +++ b/examples/with-apollo/package.json @@ -1,16 +1,19 @@ { "name": "with-apollo", - "version": "1.0.1", + "version": "2.0.0", "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "dependencies": { - "graphql": "^0.9.1", + "graphql": "^0.9.3", + "isomorphic-fetch": "^2.2.1", "next": "latest", - "react": "^15.4.2", - "react-apollo": "^1.0.0-rc.3" + "prop-types": "^15.5.8", + "react": "^15.5.4", + "react-dom": "^15.5.4", + "react-apollo": "^1.1.3" }, "author": "", "license": "ISC"