mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
* [fix] apollo-redux: Separate out entire example #3463 Apollo and Redux are completely separate ways of managing state. This example serves as a conduit if you were using Apollo 1.X with Redux, and are migrating to Apollo 2.x, however, you have chosen not to manage your entire application state within Apollo (`apollo-link-state`). There is no "withData" function that allows you to call either/or. You must call "withRedux" and/or "withApollo" on your Component. They can be combined in the example at `index.js` or remain separate as seen in `apollo.js` and `redux.js`. Going forward, this example may go the way of the dodo. * [chore] reformat code to match next syntax Localize prettier sometimes has a mind of its own. 😄️ * Fix linting
This commit is contained in:
parent
053a248c44
commit
ab889369d5
|
@ -35,7 +35,9 @@ now
|
|||
```
|
||||
|
||||
## The idea behind the example
|
||||
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.
|
||||
This example serves as a conduit if you were using Apollo 1.X with Redux, and are migrating to Apollo 2.x, however, you have chosen not to manage your entire application state within Apollo (`apollo-link-state`).
|
||||
|
||||
In 2.0.0, Apollo severs out-of-the-box support for redux in favor of Apollo's 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 access the redux store like you normally would using `react-redux`'s `connect`. Here's a quick example:
|
||||
|
||||
|
@ -44,7 +46,5 @@ const mapStateToProps = state => ({
|
|||
location: state.form.location,
|
||||
});
|
||||
|
||||
export default withData(connect(mapStateToProps, null)(Index));
|
||||
export default withRedux(connect(mapStateToProps, null)(Index));
|
||||
```
|
||||
|
||||
`connect` must go inside `withData` otherwise `connect` will not be able to find the store.
|
||||
|
|
38
examples/with-apollo-and-redux/components/AddCount.js
Normal file
38
examples/with-apollo-and-redux/components/AddCount.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React, {Component} from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addCount } from '../lib/store'
|
||||
|
||||
class AddCount extends Component {
|
||||
add = () => {
|
||||
this.props.addCount()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { count } = this.props
|
||||
return (
|
||||
<div>
|
||||
<h1>AddCount: <span>{count}</span></h1>
|
||||
<button onClick={this.add}>Add To Count</button>
|
||||
<style jsx>{`
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
div {
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ count }) => ({ count })
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
addCount: bindActionCreators(addCount, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AddCount)
|
|
@ -3,14 +3,16 @@ export default ({ children }) => (
|
|||
{children}
|
||||
<style jsx global>{`
|
||||
* {
|
||||
font-family: Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
|
||||
font-family: Menlo, Monaco, 'Lucida Console', 'Liberation Mono',
|
||||
'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New',
|
||||
monospace, serif;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 25px 50px;
|
||||
}
|
||||
a {
|
||||
color: #22BAD9;
|
||||
color: #22bad9;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
|
@ -22,15 +24,15 @@ export default ({ children }) => (
|
|||
}
|
||||
button {
|
||||
align-items: center;
|
||||
background-color: #22BAD9;
|
||||
background-color: #22bad9;
|
||||
border: 0;
|
||||
color: white;
|
||||
display: flex;
|
||||
padding: 5px 7px;
|
||||
}
|
||||
button:active {
|
||||
background-color: #1B9DB7;
|
||||
transition: background-color .3s
|
||||
background-color: #1b9db7;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
button:focus {
|
||||
outline: none;
|
||||
|
|
23
examples/with-apollo-and-redux/components/Clock.js
Normal file
23
examples/with-apollo-and-redux/components/Clock.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default ({ lastUpdate, light }) => {
|
||||
return (
|
||||
<div className={light ? 'light' : ''}>
|
||||
{format(new Date(lastUpdate))}
|
||||
<style jsx>{`
|
||||
div {
|
||||
padding: 15px;
|
||||
display: inline-block;
|
||||
color: #82FA58;
|
||||
font: 50px menlo, monaco, monospace;
|
||||
background-color: #000;
|
||||
}
|
||||
.light {
|
||||
background-color: #999;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const format = t => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}`
|
||||
|
||||
const pad = n => n < 10 ? `0${n}` : n
|
|
@ -1,4 +1,4 @@
|
|||
export default ({message}) => (
|
||||
export default ({ message }) => (
|
||||
<aside>
|
||||
{message}
|
||||
<style jsx>{`
|
||||
|
|
|
@ -6,11 +6,11 @@ const Header = ({ router: { pathname } }) => (
|
|||
<Link prefetch href='/'>
|
||||
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
|
||||
</Link>
|
||||
<Link prefetch href='/about'>
|
||||
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
|
||||
<Link prefetch href='/apollo'>
|
||||
<a className={pathname === '/apollo' ? 'is-active' : ''}>Apollo</a>
|
||||
</Link>
|
||||
<Link prefetch href='/blog'>
|
||||
<a className={pathname === '/blog' ? 'is-active' : ''}>Blog</a>
|
||||
<Link prefetch href='/redux'>
|
||||
<a className={pathname === '/redux' ? 'is-active' : ''}>Redux</a>
|
||||
</Link>
|
||||
<style jsx>{`
|
||||
header {
|
||||
|
|
18
examples/with-apollo-and-redux/components/Page.js
Normal file
18
examples/with-apollo-and-redux/components/Page.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { connect } from 'react-redux'
|
||||
import Clock from './Clock'
|
||||
import AddCount from './AddCount'
|
||||
|
||||
export default connect(state => state)(({ title, lastUpdate, light }) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Redux: {title}</h1>
|
||||
<Clock lastUpdate={lastUpdate} light={light} />
|
||||
<AddCount />
|
||||
<style jsx>{`
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
})
|
|
@ -1,63 +0,0 @@
|
|||
import React from 'react'
|
||||
import { withRouter } from 'next/router'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// The `graphql` wrapper executes a GraphQL query and makes the results
|
||||
// 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,21 +1,10 @@
|
|||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { Router } from '../routes'
|
||||
import ErrorMessage from './ErrorMessage'
|
||||
import PostVoteUp from './PostVoteUp'
|
||||
import PostVoteDown from './PostVoteDown'
|
||||
import PostVoteCount from './PostVoteCount'
|
||||
import PostUpvoter from './PostUpvoter'
|
||||
|
||||
const POSTS_PER_PAGE = 10
|
||||
|
||||
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
|
||||
|
@ -30,15 +19,8 @@ function PostList ({
|
|||
<li key={post.id}>
|
||||
<div>
|
||||
<span>{index + 1}. </span>
|
||||
<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} />
|
||||
<a href={post.url}>{post.title}</a>
|
||||
<PostUpvoter id={post.id} votes={post.votes} />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
@ -109,7 +91,6 @@ export const allPosts = gql`
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const allPostsQueryVars = {
|
||||
skip: 0,
|
||||
first: POSTS_PER_PAGE
|
||||
|
|
58
examples/with-apollo-and-redux/components/PostUpvoter.js
Normal file
58
examples/with-apollo-and-redux/components/PostUpvoter.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
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)
|
|
@ -1,30 +0,0 @@
|
|||
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>
|
||||
)
|
|
@ -1,12 +0,0 @@
|
|||
export default ({votes}) => (
|
||||
<span>
|
||||
{votes}
|
||||
<style jsx>{`
|
||||
span {
|
||||
padding: 0.5em 0.5em;
|
||||
font-size: 14px;
|
||||
color: inherit;
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
)
|
|
@ -1,41 +0,0 @@
|
|||
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)
|
|
@ -1,41 +0,0 @@
|
|||
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)
|
|
@ -16,7 +16,7 @@ function Submit ({ createPost }) {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h1>Submit</h1>
|
||||
<h1>Apollo: Submit</h1>
|
||||
<input placeholder='title' name='title' type='text' required />
|
||||
<input placeholder='url' name='url' type='url' required />
|
||||
<button type='submit'>Submit</button>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
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
|
||||
}),
|
||||
initialState, // Hydrate the store with server-side data
|
||||
compose(
|
||||
// Add additional middleware here
|
||||
devtools
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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(initialState)
|
||||
}
|
||||
|
||||
// Reuse store on the client-side
|
||||
if (!reduxStore) {
|
||||
reduxStore = create(initialState)
|
||||
}
|
||||
|
||||
return reduxStore
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
export default {
|
||||
redux: (state = {}, { type, payload }) => {
|
||||
switch (type) {
|
||||
case 'EXAMPLE_ACTION':
|
||||
return {
|
||||
...state
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
44
examples/with-apollo-and-redux/lib/store.js
Normal file
44
examples/with-apollo-and-redux/lib/store.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { createStore, applyMiddleware } from 'redux'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
|
||||
const exampleInitialState = {
|
||||
lastUpdate: 0,
|
||||
light: false,
|
||||
count: 0
|
||||
}
|
||||
|
||||
export const actionTypes = {
|
||||
ADD: 'ADD',
|
||||
TICK: 'TICK'
|
||||
}
|
||||
|
||||
// REDUCERS
|
||||
export const reducer = (state = exampleInitialState, action) => {
|
||||
switch (action.type) {
|
||||
case actionTypes.TICK:
|
||||
return Object.assign({}, state, { lastUpdate: action.ts, light: !!action.light })
|
||||
case actionTypes.ADD:
|
||||
return Object.assign({}, state, {
|
||||
count: state.count + 1
|
||||
})
|
||||
default: return state
|
||||
}
|
||||
}
|
||||
|
||||
// ACTIONS
|
||||
export const serverRenderClock = (isServer) => dispatch => {
|
||||
return dispatch({ type: actionTypes.TICK, light: !isServer, ts: Date.now() })
|
||||
}
|
||||
|
||||
export const startClock = () => dispatch => {
|
||||
return setInterval(() => dispatch({ type: actionTypes.TICK, light: true, ts: Date.now() }), 800)
|
||||
}
|
||||
|
||||
export const addCount = () => dispatch => {
|
||||
return dispatch({ type: actionTypes.ADD })
|
||||
}
|
||||
|
||||
export const initStore = (initialState = exampleInitialState) => {
|
||||
return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)))
|
||||
}
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
|
|||
import { ApolloProvider, getDataFromTree } from 'react-apollo'
|
||||
import Head from 'next/head'
|
||||
import initApollo from './initApollo'
|
||||
import initRedux from './initRedux'
|
||||
|
||||
// Gets the display name of a JSX component for dev tools
|
||||
function getComponentDisplayName(Component) {
|
||||
|
@ -16,18 +15,16 @@ export default ComposedComponent => {
|
|||
ComposedComponent
|
||||
)})`
|
||||
static propTypes = {
|
||||
stateApollo: PropTypes.object.isRequired
|
||||
serverState: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static async getInitialProps(ctx) {
|
||||
// Initial stateApollo with apollo (empty)
|
||||
let stateApollo = {
|
||||
// Initial serverState with apollo (empty)
|
||||
let serverState = {
|
||||
apollo: {
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
// Initial stateRedux with apollo (empty)
|
||||
let stateRedux = {}
|
||||
|
||||
// Evaluate the composed component's getInitialProps()
|
||||
let composedInitialProps = {}
|
||||
|
@ -39,7 +36,6 @@ export default ComposedComponent => {
|
|||
// and extract the resulting data
|
||||
if (!process.browser) {
|
||||
const apollo = initApollo()
|
||||
const redux = initRedux()
|
||||
|
||||
try {
|
||||
// Run all GraphQL queries
|
||||
|
@ -64,11 +60,8 @@ export default ComposedComponent => {
|
|||
// head side effect therefore need to be cleared manually
|
||||
Head.rewind()
|
||||
|
||||
// Extract query data from the Redux store
|
||||
stateRedux = redux.getState()
|
||||
|
||||
// Extract query data from the Apollo store
|
||||
stateApollo = {
|
||||
serverState = {
|
||||
apollo: {
|
||||
data: apollo.cache.extract()
|
||||
}
|
||||
|
@ -76,16 +69,14 @@ export default ComposedComponent => {
|
|||
}
|
||||
|
||||
return {
|
||||
stateApollo,
|
||||
stateRedux,
|
||||
serverState,
|
||||
...composedInitialProps
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.apollo = initApollo(props.stateApollo.apollo.data)
|
||||
this.redux = initRedux(props.stateRedux)
|
||||
this.apollo = initApollo(this.props.serverState.apollo.data)
|
||||
}
|
||||
|
||||
render() {
|
|
@ -2,24 +2,27 @@
|
|||
"name": "with-apollo-and-redux",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server.js"
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"apollo-cache-inmemory": "1.1.4",
|
||||
"apollo-client": "2.2.0",
|
||||
"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.2.0",
|
||||
"next-redux-wrapper": "^1.3.5",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.0.0",
|
||||
"react-apollo": "^2.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"redux": "^3.7.0"
|
||||
"react": "^16.2.0",
|
||||
"react-apollo": "^2.0.1",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-redux": "^5.0.6",
|
||||
"redux": "^3.7.2",
|
||||
"redux-devtools-extension": "^2.13.2",
|
||||
"redux-thunk": "^2.2.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
|
||||
export default () => (
|
||||
<App>
|
||||
<Header />
|
||||
<article>
|
||||
<h1>The Idea Behind This Example</h1>
|
||||
<p>
|
||||
<a href='http://dev.apollodata.com'>Apollo</a> 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.
|
||||
</p>
|
||||
<p>
|
||||
In this simple example, we integrate Apollo seamlessly with <a href='https://github.com/zeit/next.js'>Next</a> by wrapping our pages inside a <a href='https://facebook.github.io/react/docs/higher-order-components.html'>higher-order component (HOC)</a>. 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.
|
||||
</p>
|
||||
<p>
|
||||
On initial page load, while on the server and inside getInitialProps, we invoke the Apollo method, <a href='http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree'>getDataFromTree</a>. This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
|
||||
</p>
|
||||
<p>
|
||||
This example relies on <a href='http://graph.cool'>graph.cool</a> for its GraphQL backend.
|
||||
</p>
|
||||
</article>
|
||||
</App>
|
||||
)
|
13
examples/with-apollo-and-redux/pages/apollo.js
Normal file
13
examples/with-apollo-and-redux/pages/apollo.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
import Submit from '../components/Submit'
|
||||
import PostList from '../components/PostList'
|
||||
import withApollo from '../lib/withApollo'
|
||||
|
||||
export default withApollo(() => (
|
||||
<App>
|
||||
<Header />
|
||||
<Submit />
|
||||
<PostList />
|
||||
</App>
|
||||
))
|
|
@ -1,12 +0,0 @@
|
|||
import withData from '../../lib/withData'
|
||||
|
||||
import App from '../../components/App'
|
||||
import Header from '../../components/Header'
|
||||
import Post from '../../components/Post'
|
||||
|
||||
export default withData((props) => (
|
||||
<App>
|
||||
<Header />
|
||||
<Post />
|
||||
</App>
|
||||
))
|
|
@ -1,14 +0,0 @@
|
|||
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'
|
||||
|
||||
export default withData(() => (
|
||||
<App>
|
||||
<Header />
|
||||
<Submit />
|
||||
<PostList />
|
||||
</App>
|
||||
))
|
|
@ -1,11 +1,55 @@
|
|||
import withData from '../lib/withData'
|
||||
import React from 'react'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import {
|
||||
initStore,
|
||||
startClock,
|
||||
addCount,
|
||||
serverRenderClock
|
||||
} from '../lib/store'
|
||||
import withRedux from 'next-redux-wrapper'
|
||||
|
||||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
import Page from '../components/Page'
|
||||
import Submit from '../components/Submit'
|
||||
import PostList from '../components/PostList'
|
||||
import withApollo from '../lib/withApollo'
|
||||
|
||||
export default withData(() => (
|
||||
class Index extends React.Component {
|
||||
static getInitialProps ({ store, isServer }) {
|
||||
store.dispatch(serverRenderClock(isServer))
|
||||
store.dispatch(addCount())
|
||||
|
||||
return { isServer }
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.timer = this.props.startClock()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<App>
|
||||
<Header />
|
||||
<h1>Welcome Home.</h1>
|
||||
<Page title='Index' />
|
||||
<Submit />
|
||||
<PostList />
|
||||
</App>
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
addCount: bindActionCreators(addCount, dispatch),
|
||||
startClock: bindActionCreators(startClock, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default withRedux(initStore, null, mapDispatchToProps)(
|
||||
withApollo(Index)
|
||||
)
|
||||
|
|
48
examples/with-apollo-and-redux/pages/redux.js
Normal file
48
examples/with-apollo-and-redux/pages/redux.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React from 'react'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import {
|
||||
initStore,
|
||||
startClock,
|
||||
addCount,
|
||||
serverRenderClock
|
||||
} from '../lib/store'
|
||||
import withRedux from 'next-redux-wrapper'
|
||||
|
||||
import App from '../components/App'
|
||||
import Header from '../components/Header'
|
||||
import Page from '../components/Page'
|
||||
|
||||
class Index extends React.Component {
|
||||
static getInitialProps ({ store, isServer }) {
|
||||
store.dispatch(serverRenderClock(isServer))
|
||||
store.dispatch(addCount())
|
||||
|
||||
return { isServer }
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.timer = this.props.startClock()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<App>
|
||||
<Header />
|
||||
<Page title='Redux' />
|
||||
</App>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
addCount: bindActionCreators(addCount, dispatch),
|
||||
startClock: bindActionCreators(startClock, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default withRedux(initStore, null, mapDispatchToProps)(Index)
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
* 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')
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* server.js
|
||||
*
|
||||
* You can use the default server.js by simply running `next`,
|
||||
* but a custom one is required to do paramaterized urls like
|
||||
* blog/:slug.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* BENEVOLENT WEB LLC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const {createServer} = require('http')
|
||||
const next = require('next')
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 3000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({dev})
|
||||
const routes = require('./routes')
|
||||
|
||||
const handler = routes.getRequestHandler(app)
|
||||
app.prepare().then(() => {
|
||||
createServer(handler)
|
||||
.listen(port, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue