mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
[refactor] with-apollo-and-redux: 2.0.0 (#3484)
* [refactor] with-apollo-and-redux: 2.0.0 - This ports over `with-apollo` (w/ recent `withRouter` fix and addition for Post) along with implementing `apollo-cache-redux` #3463 - The `redux` side of things is lacking (it is the *same* as the original example) - Created a `routes.js` for use on Server and Client Side (to expand the PostList functionality) - SSR is maintained - Redid the "PostVote" a bit... sorry. 😬️ Possible todo(s): - Add in API and Clock Examples from `with-redux` to show Apollo and Redux working together a bit more - redux-saga (I personally use this, may be too opinionated for the base example though) Packages updated: - apollo-cache-redux - apollo-client-preset - graphql - graphql-anywhere - graphql-tag - isomorphic-unfetch - next-routes - prop-types - react - react-apollo - react-dom - redux * [refactor] fix linting issues When I run `yarn lint` explicitly these were caught, but not doing a build proper. Apologies on that! * [chore] 📦️ package.json: like other examples * [refactor] +apollo-cache-inmemory, -apollo-cache-redux Separation of Apollo and Redux. 😄️ We could stand to use a few actual examples of Redux, though this is a good starting block. Some other code cleanup as well.
This commit is contained in:
parent
e4acd7db59
commit
46b57a6eff
|
@ -35,9 +35,9 @@ now
|
|||
```
|
||||
|
||||
## The idea behind the example
|
||||
By default, Apollo Client creates its own internal Redux store to manage queries and their results. If you are already using Redux for the rest of your app, [you can have the client integrate with your existing store instead](http://dev.apollodata.com/react/redux.html), which is what this example does. This example is identical to the [`with-apollo`](https://github.com/zeit/next.js/tree/master/examples/with-apollo) with the exception of this Redux store integration.
|
||||
In 2.0.0, Apollo Client severs out-of-the-box support for redux in favor of Apollo's client side state management. This example aims to be an amalgamation of the [`with-apollo`](https://github.com/zeit/next.js/tree/master/examples/with-apollo) and [`with-redux`](https://github.com/zeit/next.js/tree/master/examples/with-redux) examples.
|
||||
|
||||
Note that you can acesss the redux store like you normally would using `react-redux`'s `connect` as per [here](http://dev.apollodata.com/react/redux.html#using-connect). Here's a quick example:
|
||||
Note that you can access the redux store like you normally would using `react-redux`'s `connect`. Here's a quick example:
|
||||
|
||||
```js
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -47,5 +47,4 @@ const mapStateToProps = state => ({
|
|||
export default withData(connect(mapStateToProps, null)(Index));
|
||||
```
|
||||
|
||||
`connect` must go inside `withData` otherwise `connect` will not be able to find the store.
|
||||
|
||||
`connect` must go inside `withData` otherwise `connect` will not be able to find the store.
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import Link from 'next/link'
|
||||
import { withRouter } from 'next/router'
|
||||
|
||||
export default ({ pathname }) => (
|
||||
const Header = ({ router: { pathname } }) => (
|
||||
<header>
|
||||
<Link prefetch href='/'>
|
||||
<a className={pathname === '/' && 'is-active'}>Home</a>
|
||||
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
|
||||
</Link>
|
||||
|
||||
<Link prefetch href='/about'>
|
||||
<a className={pathname === '/about' && 'is-active'}>About</a>
|
||||
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
|
||||
</Link>
|
||||
|
||||
<Link prefetch href='/blog'>
|
||||
<a className={pathname === '/blog' && 'is-active'}>Blog</a>
|
||||
<a className={pathname === '/blog' ? 'is-active' : ''}>Blog</a>
|
||||
</Link>
|
||||
|
||||
<style jsx>{`
|
||||
header {
|
||||
margin-bottom: 25px;
|
||||
|
@ -29,3 +27,5 @@ export default ({ pathname }) => (
|
|||
`}</style>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default withRouter(Header)
|
||||
|
|
|
@ -1,40 +1,63 @@
|
|||
import React from 'react'
|
||||
import { gql, graphql } from 'react-apollo'
|
||||
import PostUpvoter from './PostUpvoter'
|
||||
import { withRouter } from 'next/router'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
function Post ({ id, data: { loading, error, Post } }) {
|
||||
return (
|
||||
<section>
|
||||
<div key={Post.id}>
|
||||
<h1>{Post.title}</h1>
|
||||
<p>ID: {Post.id}<br />URL: {Post.url}</p>
|
||||
<PostUpvoter id={Post.id} votes={Post.votes} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
import ErrorMessage from './ErrorMessage'
|
||||
import PostVoteUp from './PostVoteUp'
|
||||
import PostVoteDown from './PostVoteDown'
|
||||
import PostVoteCount from './PostVoteCount'
|
||||
|
||||
function Post ({ id, data: { error, Post } }) {
|
||||
if (error) return <ErrorMessage message='Error loading blog post.' />
|
||||
if (Post) {
|
||||
return (
|
||||
<section>
|
||||
<div key={Post.id}>
|
||||
<h1>{Post.title}</h1>
|
||||
<p>ID: {Post.id}<br />URL: {Post.url}</p>
|
||||
<span>
|
||||
<PostVoteUp id={Post.id} votes={Post.votes} />
|
||||
<PostVoteCount votes={Post.votes} />
|
||||
<PostVoteDown id={Post.id} votes={Post.votes} />
|
||||
</span>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
span {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
return <div>Loading</div>
|
||||
}
|
||||
|
||||
const post = gql`
|
||||
query post($id: ID!) {
|
||||
Post(id: $id) {
|
||||
id
|
||||
title
|
||||
votes
|
||||
url
|
||||
createdAt
|
||||
}
|
||||
query post($id: ID!) {
|
||||
Post(id: $id) {
|
||||
id
|
||||
title
|
||||
votes
|
||||
url
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// The `graphql` wrapper executes a GraphQL query and makes the results
|
||||
// available on the `data` prop of the wrapped component (PostList)
|
||||
// Tip: ownProps is parent component's props
|
||||
export default graphql(post, {
|
||||
options: (ownProps) => {
|
||||
return {
|
||||
variables: {
|
||||
id: ownProps.id
|
||||
}
|
||||
// available on the `data` prop of the wrapped component (Post)
|
||||
const ComponentWithMutation = graphql(post, {
|
||||
options: ({ router: { query } }) => ({
|
||||
variables: {
|
||||
id: query.id
|
||||
}
|
||||
}
|
||||
}),
|
||||
props: ({ data }) => ({
|
||||
data
|
||||
})
|
||||
})(Post)
|
||||
|
||||
export default withRouter(ComponentWithMutation)
|
||||
|
|
|
@ -1,28 +1,56 @@
|
|||
import { gql, graphql } from 'react-apollo'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { Router } from '../routes'
|
||||
import ErrorMessage from './ErrorMessage'
|
||||
import PostUpvoter from './PostUpvoter'
|
||||
import Link from 'next/link'
|
||||
import PostVoteUp from './PostVoteUp'
|
||||
import PostVoteDown from './PostVoteDown'
|
||||
import PostVoteCount from './PostVoteCount'
|
||||
|
||||
const POSTS_PER_PAGE = 10
|
||||
|
||||
function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMorePosts }) {
|
||||
function handleClick (event, id) {
|
||||
event.preventDefault()
|
||||
// With route name and params
|
||||
// Router.pushRoute('blog/entry', { id: id })
|
||||
// With route URL
|
||||
Router.pushRoute(`/blog/${id}`)
|
||||
}
|
||||
|
||||
function PostList ({
|
||||
data: { loading, error, allPosts, _allPostsMeta },
|
||||
loadMorePosts
|
||||
}) {
|
||||
if (error) return <ErrorMessage message='Error loading posts.' />
|
||||
if (allPosts && allPosts.length) {
|
||||
const areMorePosts = allPosts.length < _allPostsMeta.count
|
||||
return (
|
||||
<section>
|
||||
<ul>
|
||||
{allPosts.map((post, index) =>
|
||||
{allPosts.map((post, index) => (
|
||||
<li key={post.id}>
|
||||
<div>
|
||||
<span>{index + 1}. </span>
|
||||
<Link href={{ pathname: '/blog/' + post.id }}><a>{post.title}</a></Link>
|
||||
<PostUpvoter id={post.id} votes={post.votes} />
|
||||
<a
|
||||
href={`/blog/${post.id}`}
|
||||
onClick={(event) => handleClick(event, post.id)}
|
||||
>
|
||||
{post.title}
|
||||
</a>
|
||||
<PostVoteUp id={post.id} votes={post.votes} />
|
||||
<PostVoteCount votes={post.votes} />
|
||||
<PostVoteDown id={post.id} votes={post.votes} />
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
{areMorePosts ? <button onClick={() => loadMorePosts()}> {loading ? 'Loading...' : 'Show More'} </button> : ''}
|
||||
{areMorePosts ? (
|
||||
<button onClick={() => loadMorePosts()}>
|
||||
{' '}
|
||||
{loading ? 'Loading...' : 'Show More'}{' '}
|
||||
</button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<style jsx>{`
|
||||
section {
|
||||
padding-bottom: 20px;
|
||||
|
@ -55,7 +83,7 @@ function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMore
|
|||
border-style: solid;
|
||||
border-width: 6px 4px 0 4px;
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
content: "";
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
width: 0;
|
||||
|
@ -67,7 +95,7 @@ function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMore
|
|||
return <div>Loading</div>
|
||||
}
|
||||
|
||||
const allPosts = gql`
|
||||
export const allPosts = gql`
|
||||
query allPosts($first: Int!, $skip: Int!) {
|
||||
allPosts(orderBy: createdAt_DESC, first: $first, skip: $skip) {
|
||||
id
|
||||
|
@ -75,21 +103,23 @@ const allPosts = gql`
|
|||
votes
|
||||
url
|
||||
createdAt
|
||||
},
|
||||
}
|
||||
_allPostsMeta {
|
||||
count
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const allPostsQueryVars = {
|
||||
skip: 0,
|
||||
first: POSTS_PER_PAGE
|
||||
}
|
||||
|
||||
// The `graphql` wrapper executes a GraphQL query and makes the results
|
||||
// available on the `data` prop of the wrapped component (PostList)
|
||||
export default graphql(allPosts, {
|
||||
options: {
|
||||
variables: {
|
||||
skip: 0,
|
||||
first: POSTS_PER_PAGE
|
||||
}
|
||||
variables: allPostsQueryVars
|
||||
},
|
||||
props: ({ data }) => ({
|
||||
data,
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import React from 'react'
|
||||
import { gql, graphql } from 'react-apollo'
|
||||
|
||||
function PostUpvoter ({ upvote, votes, id }) {
|
||||
return (
|
||||
<button onClick={() => upvote(id, votes + 1)}>
|
||||
{votes}
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e4e4e4;
|
||||
color: #000;
|
||||
}
|
||||
button:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-color: transparent transparent #000000 transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 4px 6px 4px;
|
||||
content: "";
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
const upvotePost = gql`
|
||||
mutation updatePost($id: ID!, $votes: Int) {
|
||||
updatePost(id: $id, votes: $votes) {
|
||||
id
|
||||
__typename
|
||||
votes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(upvotePost, {
|
||||
props: ({ ownProps, mutate }) => ({
|
||||
upvote: (id, votes) => mutate({
|
||||
variables: { id, votes },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updatePost: {
|
||||
__typename: 'Post',
|
||||
id: ownProps.id,
|
||||
votes: ownProps.votes + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})(PostUpvoter)
|
30
examples/with-apollo-and-redux/components/PostVoteButton.js
Normal file
30
examples/with-apollo-and-redux/components/PostVoteButton.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
export default ({onClickHandler, className}) => (
|
||||
<button className={className} onClick={() => onClickHandler()}>
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e4e4e4;
|
||||
color: #000;
|
||||
}
|
||||
button:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-color: transparent transparent #000000 transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 4px 6px 4px;
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 0px;
|
||||
width: 0;
|
||||
}
|
||||
.downvote {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.upvote {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
)
|
12
examples/with-apollo-and-redux/components/PostVoteCount.js
Normal file
12
examples/with-apollo-and-redux/components/PostVoteCount.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default ({votes}) => (
|
||||
<span>
|
||||
{votes}
|
||||
<style jsx>{`
|
||||
span {
|
||||
padding: 0.5em 0.5em;
|
||||
font-size: 14px;
|
||||
color: inherit;
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
)
|
41
examples/with-apollo-and-redux/components/PostVoteDown.js
Normal file
41
examples/with-apollo-and-redux/components/PostVoteDown.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import PostVoteButton from './PostVoteButton'
|
||||
|
||||
function PostVoteDown ({ downvote, votes, id }) {
|
||||
return (
|
||||
<PostVoteButton
|
||||
className='downvote'
|
||||
onClickHandler={() => downvote(id, votes - 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const downvotePost = gql`
|
||||
mutation updatePost($id: ID!, $votes: Int) {
|
||||
updatePost(id: $id, votes: $votes) {
|
||||
id
|
||||
__typename
|
||||
votes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(downvotePost, {
|
||||
props: ({ ownProps, mutate }) => ({
|
||||
downvote: (id, votes) =>
|
||||
mutate({
|
||||
variables: { id, votes },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updatePost: {
|
||||
__typename: 'Post',
|
||||
id: ownProps.id,
|
||||
votes: ownProps.votes - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})(PostVoteDown)
|
41
examples/with-apollo-and-redux/components/PostVoteUp.js
Normal file
41
examples/with-apollo-and-redux/components/PostVoteUp.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import PostVoteButton from './PostVoteButton'
|
||||
|
||||
function PostVoteUp ({ upvote, votes, id }) {
|
||||
return (
|
||||
<PostVoteButton
|
||||
className='upvote'
|
||||
onClickHandler={() => upvote(id, votes + 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const upvotePost = gql`
|
||||
mutation updatePost($id: ID!, $votes: Int) {
|
||||
updatePost(id: $id, votes: $votes) {
|
||||
id
|
||||
__typename
|
||||
votes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(upvotePost, {
|
||||
props: ({ ownProps, mutate }) => ({
|
||||
upvote: (id, votes) =>
|
||||
mutate({
|
||||
variables: { id, votes },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updatePost: {
|
||||
__typename: 'Post',
|
||||
id: ownProps.id,
|
||||
votes: ownProps.votes + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})(PostVoteUp)
|
|
@ -1,34 +1,24 @@
|
|||
import { gql, graphql } from 'react-apollo'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { allPosts, allPostsQueryVars } from './PostList'
|
||||
|
||||
function Submit ({ createPost }) {
|
||||
function handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
function handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
|
||||
let title = e.target.elements.title.value
|
||||
let url = e.target.elements.url.value
|
||||
const form = event.target
|
||||
|
||||
if (title === '' || url === '') {
|
||||
window.alert('Both fields are required.')
|
||||
return false
|
||||
}
|
||||
const formData = new window.FormData(form)
|
||||
createPost(formData.get('title'), formData.get('url'))
|
||||
|
||||
// prepend http if missing from url
|
||||
if (!url.match(/^[a-zA-Z]+:\/\//)) {
|
||||
url = `http://${url}`
|
||||
}
|
||||
|
||||
createPost(title, url)
|
||||
|
||||
// reset form
|
||||
e.target.elements.title.value = ''
|
||||
e.target.elements.url.value = ''
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h1>Submit</h1>
|
||||
<input placeholder='title' name='title' />
|
||||
<input placeholder='url' name='url' />
|
||||
<input placeholder='title' name='title' type='text' required />
|
||||
<input placeholder='url' name='url' type='url' required />
|
||||
<button type='submit'>Submit</button>
|
||||
<style jsx>{`
|
||||
form {
|
||||
|
@ -62,17 +52,23 @@ const createPost = gql`
|
|||
|
||||
export default graphql(createPost, {
|
||||
props: ({ mutate }) => ({
|
||||
createPost: (title, url) => mutate({
|
||||
variables: { title, url },
|
||||
updateQueries: {
|
||||
allPosts: (previousResult, { mutationResult }) => {
|
||||
const newPost = mutationResult.data.createPost
|
||||
return Object.assign({}, previousResult, {
|
||||
// Append the new post
|
||||
allPosts: [newPost, ...previousResult.allPosts]
|
||||
createPost: (title, url) =>
|
||||
mutate({
|
||||
variables: { title, url },
|
||||
update: (proxy, { data: { createPost } }) => {
|
||||
const data = proxy.readQuery({
|
||||
query: allPosts,
|
||||
variables: allPostsQueryVars
|
||||
})
|
||||
proxy.writeQuery({
|
||||
query: allPosts,
|
||||
data: {
|
||||
...data,
|
||||
allPosts: [createPost, ...data.allPosts]
|
||||
},
|
||||
variables: allPostsQueryVars
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})(Submit)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { ApolloClient, createNetworkInterface } from 'react-apollo'
|
||||
import { ApolloClient } from 'apollo-client'
|
||||
import { HttpLink } from 'apollo-link-http'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
|
||||
let apolloClient = null
|
||||
|
@ -8,28 +10,28 @@ if (!process.browser) {
|
|||
global.fetch = fetch
|
||||
}
|
||||
|
||||
function create () {
|
||||
function create(initialState) {
|
||||
return new ApolloClient({
|
||||
connectToDevTools: process.browser,
|
||||
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
||||
networkInterface: createNetworkInterface({
|
||||
link: new HttpLink({
|
||||
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
|
||||
opts: { // Additional fetch() options like `credentials` or `headers`
|
||||
credentials: 'same-origin'
|
||||
}
|
||||
})
|
||||
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
||||
}),
|
||||
cache: new InMemoryCache().restore(initialState || {})
|
||||
})
|
||||
}
|
||||
|
||||
export default function initApollo () {
|
||||
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()
|
||||
return create(initialState)
|
||||
}
|
||||
|
||||
// Reuse client on the client-side
|
||||
if (!apolloClient) {
|
||||
apolloClient = create()
|
||||
apolloClient = create(initialState)
|
||||
}
|
||||
|
||||
return apolloClient
|
||||
|
|
|
@ -12,27 +12,26 @@ if (process.browser && window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|||
function create (apollo, initialState = {}) {
|
||||
return createStore(
|
||||
combineReducers({ // Setup reducers
|
||||
...reducers,
|
||||
apollo: apollo.reducer()
|
||||
...reducers
|
||||
}),
|
||||
initialState, // Hydrate the store with server-side data
|
||||
compose(
|
||||
applyMiddleware(apollo.middleware()), // Add additional middleware here
|
||||
// Add additional middleware here
|
||||
devtools
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default function initRedux (apollo, initialState) {
|
||||
export default function initRedux (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)
|
||||
return create(initialState)
|
||||
}
|
||||
|
||||
// Reuse store on the client-side
|
||||
if (!reduxStore) {
|
||||
reduxStore = create(apollo, initialState)
|
||||
reduxStore = create(initialState)
|
||||
}
|
||||
|
||||
return reduxStore
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
example: (state = {}, { type, payload }) => {
|
||||
redux: (state = {}, { type, payload }) => {
|
||||
switch (type) {
|
||||
case 'EXAMPLE_ACTION':
|
||||
return {
|
||||
|
|
|
@ -6,19 +6,28 @@ import initApollo from './initApollo'
|
|||
import initRedux from './initRedux'
|
||||
|
||||
// Gets the display name of a JSX component for dev tools
|
||||
function getComponentDisplayName (Component) {
|
||||
function getComponentDisplayName(Component) {
|
||||
return Component.displayName || Component.name || 'Unknown'
|
||||
}
|
||||
|
||||
export default ComposedComponent => {
|
||||
return class WithData extends React.Component {
|
||||
static displayName = `WithData(${getComponentDisplayName(ComposedComponent)})`
|
||||
static displayName = `WithData(${getComponentDisplayName(
|
||||
ComposedComponent
|
||||
)})`
|
||||
static propTypes = {
|
||||
serverState: PropTypes.object.isRequired
|
||||
stateApollo: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static async getInitialProps (ctx) {
|
||||
let serverState = {}
|
||||
static async getInitialProps(ctx) {
|
||||
// Initial stateApollo with apollo (empty)
|
||||
let stateApollo = {
|
||||
apollo: {
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
// Initial stateRedux with apollo (empty)
|
||||
let stateRedux = {}
|
||||
|
||||
// Evaluate the composed component's getInitialProps()
|
||||
let composedInitialProps = {}
|
||||
|
@ -30,18 +39,21 @@ export default ComposedComponent => {
|
|||
// 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}
|
||||
const redux = initRedux()
|
||||
|
||||
try {
|
||||
// Run all GraphQL queries
|
||||
await getDataFromTree(
|
||||
// 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>
|
||||
<ApolloProvider client={apollo}>
|
||||
<ComposedComponent {...composedInitialProps} />
|
||||
</ApolloProvider>,
|
||||
{
|
||||
router: {
|
||||
asPath: ctx.asPath,
|
||||
pathname: ctx.pathname,
|
||||
query: ctx.query
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
// Prevent Apollo Client GraphQL errors from crashing SSR.
|
||||
|
@ -52,35 +64,33 @@ export default ComposedComponent => {
|
|||
// head side effect therefore need to be cleared manually
|
||||
Head.rewind()
|
||||
|
||||
// Extract query data from the store
|
||||
const state = redux.getState()
|
||||
// Extract query data from the Redux store
|
||||
stateRedux = 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: { // Only include the Apollo data state
|
||||
data: state.apollo.data
|
||||
// Extract query data from the Apollo store
|
||||
stateApollo = {
|
||||
apollo: {
|
||||
data: apollo.cache.extract()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
serverState,
|
||||
stateApollo,
|
||||
stateRedux,
|
||||
...composedInitialProps
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.apollo = initApollo()
|
||||
this.redux = initRedux(this.apollo, this.props.serverState)
|
||||
this.apollo = initApollo(props.stateApollo.apollo.data)
|
||||
this.redux = initRedux(props.stateRedux)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
// No need to use the Redux Provider
|
||||
// because Apollo sets up the store for us
|
||||
<ApolloProvider client={this.apollo} store={this.redux}>
|
||||
<ApolloProvider client={this.apollo}>
|
||||
<ComposedComponent {...this.props} />
|
||||
</ApolloProvider>
|
||||
)
|
||||
|
|
|
@ -7,15 +7,19 @@
|
|||
"start": "NODE_ENV=production node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql": "^0.9.3",
|
||||
"apollo-cache-inmemory": "1.1.4",
|
||||
"apollo-client-preset": "^1.0.4",
|
||||
"graphql": "^0.11.7",
|
||||
"graphql-anywhere": "^4.0.2",
|
||||
"graphql-tag": "^2.5.0",
|
||||
"isomorphic-unfetch": "^2.0.0",
|
||||
"next": "latest",
|
||||
"next-routes": "^1.1.0",
|
||||
"prop-types": "^15.5.8",
|
||||
"next-routes": "^1.2.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.0.0",
|
||||
"react-apollo": "^1.1.3",
|
||||
"react-apollo": "^2.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"redux": "^3.6.0"
|
||||
"redux": "^3.7.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
|
||||
export default (props) => (
|
||||
export default () => (
|
||||
<App>
|
||||
<Header pathname={props.url.pathname} />
|
||||
<Header />
|
||||
<article>
|
||||
<h1>The Idea Behind This Example</h1>
|
||||
<p>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import withData from '../../lib/withData'
|
||||
|
||||
import App from '../../components/App'
|
||||
import Header from '../../components/Header'
|
||||
import Post from '../../components/Post'
|
||||
import withData from '../../lib/withData'
|
||||
|
||||
export default withData((props) => (
|
||||
<App>
|
||||
<Header pathname={props.url.pathname} />
|
||||
<Post id={props.url.query.id} />
|
||||
<Header />
|
||||
<Post />
|
||||
</App>
|
||||
))
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import withData from '../../lib/withData'
|
||||
|
||||
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((props) => (
|
||||
export default withData(() => (
|
||||
<App>
|
||||
<Header pathname={props.url.pathname} />
|
||||
<Header />
|
||||
<Submit />
|
||||
<PostList />
|
||||
</App>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
import withData from '../lib/withData'
|
||||
|
||||
export default withData((props) => (
|
||||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
|
||||
export default withData(() => (
|
||||
<App>
|
||||
<Header pathname={props.url.pathname} />
|
||||
<Header />
|
||||
<h1>Welcome Home.</h1>
|
||||
</App>
|
||||
))
|
||||
|
|
8
examples/with-apollo-and-redux/routes.js
Normal file
8
examples/with-apollo-and-redux/routes.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Parameterized Routing with next-route
|
||||
*
|
||||
* Benefits: Less code, and easily handles complex url structures
|
||||
**/
|
||||
const routes = (module.exports = require('next-routes')())
|
||||
|
||||
routes.add('blog/entry', '/blog/:id')
|
|
@ -19,15 +19,7 @@ const next = require('next')
|
|||
const port = parseInt(process.env.PORT, 10) || 3000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({dev})
|
||||
|
||||
/**
|
||||
* Parameterized Routing with next-route
|
||||
*
|
||||
* Benefits: Less code, and easily handles complex url structures
|
||||
*/
|
||||
|
||||
const routes = require('next-routes')()
|
||||
routes.add('blog/entry', '/blog/:id')
|
||||
const routes = require('./routes')
|
||||
|
||||
const handler = routes.getRequestHandler(app)
|
||||
app.prepare().then(() => {
|
||||
|
|
Loading…
Reference in a new issue