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

@ -1,4 +1,4 @@
export default ({message}) => ( export default ({ message }) => (
<aside> <aside>
{message} {message}
<style jsx>{` <style jsx>{`

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

View file

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

View file

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

View file

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

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