1
0
Fork 0
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:
Tim Neutkens 2018-05-07 15:02:56 +02:00 committed by GitHub
parent 4881cd346b
commit 6c0091fb7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 117 deletions

View file

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

View file

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

View 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} />
}
}
}

View file

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

View 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)

View file

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