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

View file

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

View file

@ -3,13 +3,11 @@ import Link from 'next/link'
export default ({ 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>
<style jsx>{`
header {
margin-bottom: 25px;

View file

@ -5,14 +5,17 @@ import PostUpvoter from './PostUpvoter'
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 (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>
@ -20,9 +23,16 @@ function PostList ({ data: { loading, error, allPosts, _allPostsMeta }, loadMore
<PostUpvoter 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 +65,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;
@ -75,7 +85,7 @@ export const allPosts = gql`
votes
url
createdAt
},
}
_allPostsMeta {
count
}

View file

@ -20,7 +20,7 @@ function PostUpvoter ({ upvote, votes, id }) {
border-color: transparent transparent #000000 transparent;
border-style: solid;
border-width: 0 4px 6px 4px;
content: "";
content: '';
height: 0;
margin-right: 5px;
width: 0;
@ -42,16 +42,17 @@ const upvotePost = gql`
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
upvote: (id, votes) =>
mutate({
variables: { id, votes },
optimisticResponse: {
__typename: 'Mutation',
updatePost: {
__typename: 'Post',
id: ownProps.id,
votes: ownProps.votes + 1
}
}
}
})
})
})
})(PostUpvoter)

View file

@ -1,36 +1,24 @@
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
import {allPosts, allPostsQueryVars} from './PostList'
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 {
@ -64,12 +52,23 @@ const createPost = gql`
export default graphql(createPost, {
props: ({ mutate }) => ({
createPost: (title, url) => mutate({
variables: { title, url },
update: (proxy, { data: { createPost } }) => {
const data = proxy.readQuery({ query: allPosts, variables: allPostsQueryVars })
proxy.writeQuery({ query: allPosts, data: {allPosts: [createPost, ...data.allPosts]}, variables: allPostsQueryVars })
}
})
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)

View file

@ -10,7 +10,7 @@ if (!process.browser) {
global.fetch = fetch
}
function create (initialState) {
function create(initialState) {
return new ApolloClient({
connectToDevTools: process.browser,
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)
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
// isn't shared between connections (which would be bad)
if (!process.browser) {

View file

@ -5,23 +5,25 @@ import Head from 'next/head'
import initApollo from './initApollo'
// 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
}
static async getInitialProps (ctx) {
static async getInitialProps(ctx) {
// Initial serverState with apollo (empty)
let serverState = {
let serverState = {
apollo: {
data: { }
}
data: {}
}
}
// Evaluate the composed component's getInitialProps()
@ -35,7 +37,7 @@ export default ComposedComponent => {
if (!process.browser) {
const apollo = initApollo()
// 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 {
// Run all GraphQL queries
await getDataFromTree(
@ -66,12 +68,12 @@ export default ComposedComponent => {
}
}
constructor (props) {
constructor(props) {
super(props)
this.apollo = initApollo(this.props.serverState.apollo.data)
}
render () {
render() {
return (
<ApolloProvider client={this.apollo}>
<ComposedComponent {...this.props} />

View file

@ -7,16 +7,16 @@
"start": "next start"
},
"dependencies": {
"apollo-client-preset": "^1.0.1",
"graphql": "^0.11.7",
"graphql-anywhere": "^3.1.0",
"graphql-tag": "^2.5.0",
"isomorphic-unfetch": "^2.0.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",
"prop-types": "^15.5.8",
"react": "^16.0.0",
"react-apollo": "^2.0.0",
"react-dom": "^16.0.0"
"prop-types": "15.6.0",
"react": "16.2.0",
"react-apollo": "2.0.1",
"react-dom": "16.2.0"
},
"author": "",
"license": "ISC"

View file

@ -1,22 +1,40 @@
import App from '../components/App'
import Header from '../components/Header'
export default (props) => (
export default props => (
<App>
<Header pathname={props.url.pathname} />
<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.
<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.
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.
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.
This example relies on <a href='http://graph.cool'>graph.cool</a> for
its GraphQL backend.
</p>
</article>
</App>

View file

@ -4,7 +4,7 @@ import Submit from '../components/Submit'
import PostList from '../components/PostList'
import withData from '../lib/withData'
export default withData((props) => (
export default withData(props => (
<App>
<Header pathname={props.url.pathname} />
<Submit />