1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Many improvements to the Apollo examples (#1905)

* Many improvements to the Apollo examples

* Use static properties
This commit is contained in:
Roland Warmerdam 2017-05-10 15:23:11 -07:00 committed by Tim Neutkens
parent 3b91355c9e
commit f4d6cbfc19
13 changed files with 223 additions and 148 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
import { combineReducers } from 'redux'
export default function getReducer (client) {
return combineReducers({
apollo: client.reducer()
})
}

View file

@ -0,0 +1,12 @@
export default {
example: (state = {}, { type, payload }) => {
switch (type) {
case 'EXAMPLE_ACTION':
return {
...state
}
default:
return state
}
}
}

View file

@ -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 = (
<ApolloProvider client={client} store={store}>
<Component {...props} />
// No need to use the Redux Provider
// because Apollo sets up the store for us
<ApolloProvider client={apollo} store={redux}>
<ComposedComponent url={url} {...composedInitialProps} />
</ApolloProvider>
)
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 (
<ApolloProvider client={this.client} store={this.store}>
<Component {...this.props} />
// No need to use the Redux Provider
// because Apollo sets up the store for us
<ApolloProvider client={this.apollo} store={this.redux}>
<ComposedComponent {...this.props} />
</ApolloProvider>
)
}
}
)
}

View file

@ -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": "",

View file

@ -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
}

View file

@ -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
}

View file

@ -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 = (
<ApolloProvider client={client}>
<Component {...props} />
<ApolloProvider client={apollo}>
<ComposedComponent url={url} {...composedInitialProps} />
</ApolloProvider>
)
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 (
<ApolloProvider client={this.client}>
<Component {...this.props} />
<ApolloProvider client={this.apollo}>
<ComposedComponent {...this.props} />
</ApolloProvider>
)
}
}
)
}

View file

@ -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"