mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
[with-apollo] Fix warning about missing _allPostsMeta and more (#3397)
* Fix coding style * Fix className type * Upgrade deps * Fix coding style of lib/ * Simplify onSubmit handler * Fix missing missing _allPostsMeta warning * Follow lint rules
This commit is contained in:
parent
25005d158b
commit
48ed89f93d
|
@ -3,14 +3,16 @@ export default ({ children }) => (
|
||||||
{children}
|
{children}
|
||||||
<style jsx global>{`
|
<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 {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 25px 50px;
|
padding: 25px 50px;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #22BAD9;
|
color: #22bad9;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -22,15 +24,15 @@ export default ({ children }) => (
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #22BAD9;
|
background-color: #22bad9;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px 7px;
|
padding: 5px 7px;
|
||||||
}
|
}
|
||||||
button:active {
|
button:active {
|
||||||
background-color: #1B9DB7;
|
background-color: #1b9db7;
|
||||||
transition: background-color .3s
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default ({message}) => (
|
export default ({ message }) => (
|
||||||
<aside>
|
<aside>
|
||||||
{message}
|
{message}
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
|
|
|
@ -3,13 +3,11 @@ import Link from 'next/link'
|
||||||
export default ({ pathname }) => (
|
export default ({ pathname }) => (
|
||||||
<header>
|
<header>
|
||||||
<Link prefetch href='/'>
|
<Link prefetch href='/'>
|
||||||
<a className={pathname === '/' && 'is-active'}>Home</a>
|
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link prefetch href='/about'>
|
<Link prefetch href='/about'>
|
||||||
<a className={pathname === '/about' && 'is-active'}>About</a>
|
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
header {
|
header {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
|
|
|
@ -5,14 +5,17 @@ import PostUpvoter from './PostUpvoter'
|
||||||
|
|
||||||
const POSTS_PER_PAGE = 10
|
const POSTS_PER_PAGE = 10
|
||||||
|
|
||||||
function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMorePosts }) {
|
function PostList ({
|
||||||
|
data: { loading, error, allPosts, _allPostsMeta },
|
||||||
|
loadMorePosts
|
||||||
|
}) {
|
||||||
if (error) return <ErrorMessage message='Error loading posts.' />
|
if (error) return <ErrorMessage message='Error loading posts.' />
|
||||||
if (allPosts && allPosts.length) {
|
if (allPosts && allPosts.length) {
|
||||||
const areMorePosts = allPosts.length < _allPostsMeta.count
|
const areMorePosts = allPosts.length < _allPostsMeta.count
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<ul>
|
<ul>
|
||||||
{allPosts.map((post, index) =>
|
{allPosts.map((post, index) => (
|
||||||
<li key={post.id}>
|
<li key={post.id}>
|
||||||
<div>
|
<div>
|
||||||
<span>{index + 1}. </span>
|
<span>{index + 1}. </span>
|
||||||
|
@ -20,9 +23,16 @@ function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMore
|
||||||
<PostUpvoter id={post.id} votes={post.votes} />
|
<PostUpvoter id={post.id} votes={post.votes} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{areMorePosts ? <button onClick={() => loadMorePosts()}> {loading ? 'Loading...' : 'Show More'} </button> : ''}
|
{areMorePosts ? (
|
||||||
|
<button onClick={() => loadMorePosts()}>
|
||||||
|
{' '}
|
||||||
|
{loading ? 'Loading...' : 'Show More'}{' '}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
section {
|
section {
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
@ -55,7 +65,7 @@ function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMore
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 6px 4px 0 4px;
|
border-width: 6px 4px 0 4px;
|
||||||
border-color: #ffffff transparent transparent transparent;
|
border-color: #ffffff transparent transparent transparent;
|
||||||
content: "";
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -75,7 +85,7 @@ export const allPosts = gql`
|
||||||
votes
|
votes
|
||||||
url
|
url
|
||||||
createdAt
|
createdAt
|
||||||
},
|
}
|
||||||
_allPostsMeta {
|
_allPostsMeta {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ function PostUpvoter ({ upvote, votes, id }) {
|
||||||
border-color: transparent transparent #000000 transparent;
|
border-color: transparent transparent #000000 transparent;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0 4px 6px 4px;
|
border-width: 0 4px 6px 4px;
|
||||||
content: "";
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -42,16 +42,17 @@ const upvotePost = gql`
|
||||||
|
|
||||||
export default graphql(upvotePost, {
|
export default graphql(upvotePost, {
|
||||||
props: ({ ownProps, mutate }) => ({
|
props: ({ ownProps, mutate }) => ({
|
||||||
upvote: (id, votes) => mutate({
|
upvote: (id, votes) =>
|
||||||
variables: { id, votes },
|
mutate({
|
||||||
optimisticResponse: {
|
variables: { id, votes },
|
||||||
__typename: 'Mutation',
|
optimisticResponse: {
|
||||||
updatePost: {
|
__typename: 'Mutation',
|
||||||
__typename: 'Post',
|
updatePost: {
|
||||||
id: ownProps.id,
|
__typename: 'Post',
|
||||||
votes: ownProps.votes + 1
|
id: ownProps.id,
|
||||||
|
votes: ownProps.votes + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})(PostUpvoter)
|
})(PostUpvoter)
|
||||||
|
|
|
@ -1,36 +1,24 @@
|
||||||
import { graphql } from 'react-apollo'
|
import { graphql } from 'react-apollo'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import {allPosts, allPostsQueryVars} from './PostList'
|
import { allPosts, allPostsQueryVars } from './PostList'
|
||||||
|
|
||||||
function Submit ({ createPost }) {
|
function Submit ({ createPost }) {
|
||||||
function handleSubmit (e) {
|
function handleSubmit (event) {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
let title = e.target.elements.title.value
|
const form = event.target
|
||||||
let url = e.target.elements.url.value
|
|
||||||
|
|
||||||
if (title === '' || url === '') {
|
const formData = new window.FormData(form)
|
||||||
window.alert('Both fields are required.')
|
createPost(formData.get('title'), formData.get('url'))
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepend http if missing from url
|
form.reset()
|
||||||
if (!url.match(/^[a-zA-Z]+:\/\//)) {
|
|
||||||
url = `http://${url}`
|
|
||||||
}
|
|
||||||
|
|
||||||
createPost(title, url)
|
|
||||||
|
|
||||||
// reset form
|
|
||||||
e.target.elements.title.value = ''
|
|
||||||
e.target.elements.url.value = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<h1>Submit</h1>
|
<h1>Submit</h1>
|
||||||
<input placeholder='title' name='title' />
|
<input placeholder='title' name='title' type='text' required />
|
||||||
<input placeholder='url' name='url' />
|
<input placeholder='url' name='url' type='url' required />
|
||||||
<button type='submit'>Submit</button>
|
<button type='submit'>Submit</button>
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
form {
|
form {
|
||||||
|
@ -64,12 +52,23 @@ const createPost = gql`
|
||||||
|
|
||||||
export default graphql(createPost, {
|
export default graphql(createPost, {
|
||||||
props: ({ mutate }) => ({
|
props: ({ mutate }) => ({
|
||||||
createPost: (title, url) => mutate({
|
createPost: (title, url) =>
|
||||||
variables: { title, url },
|
mutate({
|
||||||
update: (proxy, { data: { createPost } }) => {
|
variables: { title, url },
|
||||||
const data = proxy.readQuery({ query: allPosts, variables: allPostsQueryVars })
|
update: (proxy, { data: { createPost } }) => {
|
||||||
proxy.writeQuery({ query: allPosts, data: {allPosts: [createPost, ...data.allPosts]}, variables: allPostsQueryVars })
|
const data = proxy.readQuery({
|
||||||
}
|
query: allPosts,
|
||||||
})
|
variables: allPostsQueryVars
|
||||||
|
})
|
||||||
|
proxy.writeQuery({
|
||||||
|
query: allPosts,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
allPosts: [createPost, ...data.allPosts]
|
||||||
|
},
|
||||||
|
variables: allPostsQueryVars
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})(Submit)
|
})(Submit)
|
||||||
|
|
|
@ -10,7 +10,7 @@ if (!process.browser) {
|
||||||
global.fetch = fetch
|
global.fetch = fetch
|
||||||
}
|
}
|
||||||
|
|
||||||
function create (initialState) {
|
function create(initialState) {
|
||||||
return new ApolloClient({
|
return new ApolloClient({
|
||||||
connectToDevTools: process.browser,
|
connectToDevTools: process.browser,
|
||||||
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
||||||
|
@ -18,11 +18,11 @@ function create (initialState) {
|
||||||
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
|
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
|
||||||
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
||||||
}),
|
}),
|
||||||
cache: new InMemoryCache().restore(initialState || {}),
|
cache: new InMemoryCache().restore(initialState || {})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function initApollo (initialState) {
|
export default function initApollo(initialState) {
|
||||||
// Make sure to create a new client for every server-side request so that data
|
// Make sure to create a new client for every server-side request so that data
|
||||||
// isn't shared between connections (which would be bad)
|
// isn't shared between connections (which would be bad)
|
||||||
if (!process.browser) {
|
if (!process.browser) {
|
||||||
|
|
|
@ -5,23 +5,25 @@ import Head from 'next/head'
|
||||||
import initApollo from './initApollo'
|
import initApollo from './initApollo'
|
||||||
|
|
||||||
// Gets the display name of a JSX component for dev tools
|
// Gets the display name of a JSX component for dev tools
|
||||||
function getComponentDisplayName (Component) {
|
function getComponentDisplayName(Component) {
|
||||||
return Component.displayName || Component.name || 'Unknown'
|
return Component.displayName || Component.name || 'Unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComposedComponent => {
|
export default ComposedComponent => {
|
||||||
return class WithData extends React.Component {
|
return class WithData extends React.Component {
|
||||||
static displayName = `WithData(${getComponentDisplayName(ComposedComponent)})`
|
static displayName = `WithData(${getComponentDisplayName(
|
||||||
|
ComposedComponent
|
||||||
|
)})`
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
serverState: PropTypes.object.isRequired
|
serverState: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getInitialProps (ctx) {
|
static async getInitialProps(ctx) {
|
||||||
// Initial serverState with apollo (empty)
|
// Initial serverState with apollo (empty)
|
||||||
let serverState = {
|
let serverState = {
|
||||||
apollo: {
|
apollo: {
|
||||||
data: { }
|
data: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the composed component's getInitialProps()
|
// Evaluate the composed component's getInitialProps()
|
||||||
|
@ -35,7 +37,7 @@ export default ComposedComponent => {
|
||||||
if (!process.browser) {
|
if (!process.browser) {
|
||||||
const apollo = initApollo()
|
const apollo = initApollo()
|
||||||
// Provide the `url` prop data in case a GraphQL query uses it
|
// Provide the `url` prop data in case a GraphQL query uses it
|
||||||
const url = {query: ctx.query, pathname: ctx.pathname}
|
const url = { query: ctx.query, pathname: ctx.pathname }
|
||||||
try {
|
try {
|
||||||
// Run all GraphQL queries
|
// Run all GraphQL queries
|
||||||
await getDataFromTree(
|
await getDataFromTree(
|
||||||
|
@ -66,12 +68,12 @@ export default ComposedComponent => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.apollo = initApollo(this.props.serverState.apollo.data)
|
this.apollo = initApollo(this.props.serverState.apollo.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ApolloProvider client={this.apollo}>
|
<ApolloProvider client={this.apollo}>
|
||||||
<ComposedComponent {...this.props} />
|
<ComposedComponent {...this.props} />
|
||||||
|
|
|
@ -7,16 +7,16 @@
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apollo-client-preset": "^1.0.1",
|
"apollo-client-preset": "1.0.4",
|
||||||
"graphql": "^0.11.7",
|
"graphql": "0.11.7",
|
||||||
"graphql-anywhere": "^3.1.0",
|
"graphql-anywhere": "4.0.2",
|
||||||
"graphql-tag": "^2.5.0",
|
"graphql-tag": "2.5.0",
|
||||||
"isomorphic-unfetch": "^2.0.0",
|
"isomorphic-unfetch": "2.0.0",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "15.6.0",
|
||||||
"react": "^16.0.0",
|
"react": "16.2.0",
|
||||||
"react-apollo": "^2.0.0",
|
"react-apollo": "2.0.1",
|
||||||
"react-dom": "^16.0.0"
|
"react-dom": "16.2.0"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
|
|
@ -1,22 +1,40 @@
|
||||||
import App from '../components/App'
|
import App from '../components/App'
|
||||||
import Header from '../components/Header'
|
import Header from '../components/Header'
|
||||||
|
|
||||||
export default (props) => (
|
export default props => (
|
||||||
<App>
|
<App>
|
||||||
<Header pathname={props.url.pathname} />
|
<Header pathname={props.url.pathname} />
|
||||||
<article>
|
<article>
|
||||||
<h1>The Idea Behind This Example</h1>
|
<h1>The Idea Behind This Example</h1>
|
||||||
<p>
|
<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.
|
<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>
|
||||||
<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.
|
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>
|
||||||
<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.
|
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>
|
||||||
<p>
|
<p>
|
||||||
This example relies on <a href='http://graph.cool'>graph.cool</a> for its GraphQL backend.
|
This example relies on <a href='http://graph.cool'>graph.cool</a> for
|
||||||
|
its GraphQL backend.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</App>
|
</App>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Submit from '../components/Submit'
|
||||||
import PostList from '../components/PostList'
|
import PostList from '../components/PostList'
|
||||||
import withData from '../lib/withData'
|
import withData from '../lib/withData'
|
||||||
|
|
||||||
export default withData((props) => (
|
export default withData(props => (
|
||||||
<App>
|
<App>
|
||||||
<Header pathname={props.url.pathname} />
|
<Header pathname={props.url.pathname} />
|
||||||
<Submit />
|
<Submit />
|
||||||
|
|
Loading…
Reference in a new issue