1
0
Fork 0
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:
Brice BERNARD 2017-12-05 19:50:45 +01:00 committed by Tim Neutkens
parent 25005d158b
commit 48ed89f93d
11 changed files with 112 additions and 82 deletions

View file

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

View file

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

View file

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

View file

@ -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,7 +42,8 @@ const upvotePost = gql`
export default graphql(upvotePost, { export default graphql(upvotePost, {
props: ({ ownProps, mutate }) => ({ props: ({ ownProps, mutate }) => ({
upvote: (id, votes) => mutate({ upvote: (id, votes) =>
mutate({
variables: { id, votes }, variables: { id, votes },
optimisticResponse: { optimisticResponse: {
__typename: 'Mutation', __typename: 'Mutation',

View file

@ -3,34 +3,22 @@ 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,11 +52,22 @@ const createPost = gql`
export default graphql(createPost, { export default graphql(createPost, {
props: ({ mutate }) => ({ props: ({ mutate }) => ({
createPost: (title, url) => mutate({ createPost: (title, url) =>
mutate({
variables: { title, url }, variables: { title, url },
update: (proxy, { data: { createPost } }) => { update: (proxy, { data: { createPost } }) => {
const data = proxy.readQuery({ query: allPosts, variables: allPostsQueryVars }) const data = proxy.readQuery({
proxy.writeQuery({ query: allPosts, data: {allPosts: [createPost, ...data.allPosts]}, variables: allPostsQueryVars }) query: allPosts,
variables: allPostsQueryVars
})
proxy.writeQuery({
query: allPosts,
data: {
...data,
allPosts: [createPost, ...data.allPosts]
},
variables: allPostsQueryVars
})
} }
}) })
}) })

View file

@ -18,7 +18,7 @@ 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 || {})
}) })
} }

View file

@ -11,7 +11,9 @@ function getComponentDisplayName (Component) {
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
} }

View file

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

View file

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

View file

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