mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add new apollo example using _app.js (#4286)
* Add new apollo example * Update readme * Update with-apollo to use _app.js * Move withData to _app * Make SSR work again * Remove withData * Use HOC to provide apolloClient * Spread pageprops
This commit is contained in:
parent
4881cd346b
commit
6c0091fb7a
|
@ -46,11 +46,8 @@ now
|
|||
|
||||
[Apollo](http://dev.apollodata.com) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server.
|
||||
|
||||
In this simple example, we integrate Apollo seamlessly with Next by wrapping our *pages* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
|
||||
In this simple example, we integrate Apollo seamlessly with Next by wrapping our *pages/_app.js* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
|
||||
|
||||
On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
|
||||
|
||||
This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
|
||||
|
||||
### Note:
|
||||
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.
|
||||
|
|
|
@ -102,23 +102,25 @@ export default graphql(allPosts, {
|
|||
options: {
|
||||
variables: allPostsQueryVars
|
||||
},
|
||||
props: ({ data }) => ({
|
||||
data,
|
||||
loadMorePosts: () => {
|
||||
return data.fetchMore({
|
||||
variables: {
|
||||
skip: data.allPosts.length
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) {
|
||||
return previousResult
|
||||
props: ({ data }) => {
|
||||
return ({
|
||||
data,
|
||||
loadMorePosts: () => {
|
||||
return data.fetchMore({
|
||||
variables: {
|
||||
skip: data.allPosts.length
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) {
|
||||
return previousResult
|
||||
}
|
||||
return Object.assign({}, previousResult, {
|
||||
// Append the new posts results to the old one
|
||||
allPosts: [...previousResult.allPosts, ...fetchMoreResult.allPosts]
|
||||
})
|
||||
}
|
||||
return Object.assign({}, previousResult, {
|
||||
// Append the new posts results to the old one
|
||||
allPosts: [...previousResult.allPosts, ...fetchMoreResult.allPosts]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})(PostList)
|
||||
|
|
66
examples/with-apollo/lib/with-apollo-client.js
Normal file
66
examples/with-apollo/lib/with-apollo-client.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import initApollo from './init-apollo'
|
||||
import Head from 'next/head'
|
||||
import { getDataFromTree } from 'react-apollo'
|
||||
import propTypes from 'prop-types'
|
||||
|
||||
export default (App) => {
|
||||
return class Apollo extends React.Component {
|
||||
static displayName = 'withApollo(App)'
|
||||
static async getInitialProps (ctx) {
|
||||
const { Component, router } = ctx
|
||||
|
||||
let appProps = {}
|
||||
if (App.getInitialProps) {
|
||||
appProps = await App.getInitialProps(ctx)
|
||||
}
|
||||
|
||||
const apolloState = {}
|
||||
|
||||
// Run all GraphQL queries in the component tree
|
||||
// and extract the resulting data
|
||||
const apollo = initApollo()
|
||||
try {
|
||||
// Run all GraphQL queries
|
||||
await getDataFromTree(
|
||||
<App
|
||||
{...appProps}
|
||||
Component={Component}
|
||||
router={router}
|
||||
apolloState={apolloState}
|
||||
apolloClient={apollo}
|
||||
/>
|
||||
)
|
||||
} 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
|
||||
console.error('Error while running `getDataFromTree`', error)
|
||||
}
|
||||
|
||||
if (!process.browser) {
|
||||
// getDataFromTree does not call componentWillUnmount
|
||||
// head side effect therefore need to be cleared manually
|
||||
Head.rewind()
|
||||
}
|
||||
|
||||
// Extract query data from the Apollo store
|
||||
apolloState.data = apollo.cache.extract()
|
||||
|
||||
return {
|
||||
...appProps,
|
||||
apolloState
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
// `getDataFromTree` renders the component first, the client is passed off as a property.
|
||||
// After that rendering is done using Next's normal rendering pipeline
|
||||
this.apolloClient = props.apolloClient || initApollo(props.apolloState.data)
|
||||
}
|
||||
|
||||
render () {
|
||||
return <App {...this.props} apolloClient={this.apolloClient} />
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
||||
import Head from 'next/head'
|
||||
import initApollo from './initApollo'
|
||||
|
||||
// Gets the display name of a JSX component for dev tools
|
||||
function getComponentDisplayName(Component) {
|
||||
return Component.displayName || Component.name || 'Unknown'
|
||||
}
|
||||
|
||||
export default ComposedComponent => {
|
||||
return class WithData extends React.Component {
|
||||
static displayName = `WithData(${getComponentDisplayName(
|
||||
ComposedComponent
|
||||
)})`
|
||||
static propTypes = {
|
||||
serverState: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static async getInitialProps(ctx) {
|
||||
// Initial serverState with apollo (empty)
|
||||
let serverState
|
||||
|
||||
// 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
|
||||
const apollo = initApollo()
|
||||
try {
|
||||
// create the url prop which is passed to every page
|
||||
const url = {
|
||||
query: ctx.query,
|
||||
asPath: ctx.asPath,
|
||||
pathname: ctx.pathname,
|
||||
};
|
||||
|
||||
// Run all GraphQL queries
|
||||
await getDataFromTree(
|
||||
<ComposedComponent ctx={ctx} url={url} {...composedInitialProps} />,
|
||||
{
|
||||
router: {
|
||||
asPath: ctx.asPath,
|
||||
pathname: ctx.pathname,
|
||||
query: ctx.query
|
||||
},
|
||||
client: apollo
|
||||
}
|
||||
)
|
||||
} 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
|
||||
}
|
||||
|
||||
if (!process.browser) {
|
||||
// getDataFromTree does not call componentWillUnmount
|
||||
// head side effect therefore need to be cleared manually
|
||||
Head.rewind()
|
||||
}
|
||||
|
||||
// Extract query data from the Apollo store
|
||||
serverState = {
|
||||
apollo: {
|
||||
data: apollo.cache.extract()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
serverState,
|
||||
...composedInitialProps
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.apollo = initApollo(this.props.serverState.apollo.data)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ApolloProvider client={this.apollo}>
|
||||
<ComposedComponent {...this.props} />
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
17
examples/with-apollo/pages/_app.js
Normal file
17
examples/with-apollo/pages/_app.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import App, {Container} from 'next/app'
|
||||
import React from 'react'
|
||||
import withApolloClient from '../lib/with-apollo-client'
|
||||
import { ApolloProvider } from 'react-apollo'
|
||||
|
||||
class MyApp extends App {
|
||||
render () {
|
||||
const {Component, pageProps, apolloClient} = this.props
|
||||
return <Container>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<Component {...pageProps} />
|
||||
</ApolloProvider>
|
||||
</Container>
|
||||
}
|
||||
}
|
||||
|
||||
export default withApolloClient(MyApp)
|
|
@ -2,12 +2,11 @@ import App from '../components/App'
|
|||
import Header from '../components/Header'
|
||||
import Submit from '../components/Submit'
|
||||
import PostList from '../components/PostList'
|
||||
import withData from '../lib/withData'
|
||||
|
||||
export default withData(() => (
|
||||
export default () => (
|
||||
<App>
|
||||
<Header />
|
||||
<Submit />
|
||||
<PostList />
|
||||
</App>
|
||||
))
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue