Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,13 @@ On the client side, we have a parameter call `as` on `<Link>` that _decorates_ t
It’s up to you. `getInitialProps` is an `async` function (or a regular function that returns a `Promise`). It can retrieve data from anywhere.
</details>

<details>
<summary>Can I use it with GraphQL?</summary>

Yes! Here's an example with [Apollo](./examples/with-apollo).

</details>

<details>
<summary>Can I use it with Redux?</summary>

Expand Down
26 changes: 26 additions & 0 deletions examples/with-apollo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Apollo Example
## Demo
https://next-with-apollo.now.sh

## How to use
Install it and run

```bash
npm install
npm run dev
```

Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))

```bash
now
```

## The idea behind the example
Apollo 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.

In this simple example, we integrate Apollo seamlessly with Next by wrapping our *pages* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). 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.

On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.

This example relies on [graph.cool](graph.cool) for its GraphQL backend.
40 changes: 40 additions & 0 deletions examples/with-apollo/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default ({ children }) => (
<main>
{children}
<style jsx global>{`
* {
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;
}
p {
font-size: 14px;
line-height: 24px;
}
article {
margin: 0 auto;
max-width: 650px;
}
button {
align-items: center;
background-color: #22BAD9;
border: 0;
color: white;
display: flex;
padding: 5px 7px;
}
button:active {
background-color: #1B9DB7;
transition: background-color .3s
}
button:focus {
outline: none;
}
`}</style>
</main>
)
27 changes: 27 additions & 0 deletions examples/with-apollo/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from 'next/prefetch'

export default ({ pathname }) => (
<header>
<Link href='/'>
<a className={pathname === '/' && 'is-active'}>Home</a>
</Link>

<Link href='/about'>
<a className={pathname === '/about' && 'is-active'}>About</a>
</Link>

<style jsx>{`
header {
margin-bottom: 25px;
}
a {
font-size: 14px;
margin-right: 15px;
text-decoration: none;
}
.is-active {
text-decoration: underline;
}
`}</style>
</header>
)
114 changes: 114 additions & 0 deletions examples/with-apollo/components/PostList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'
import PostUpvoter from './PostUpvoter'

const POSTS_PER_PAGE = 10

function PostList ({ data: { allPosts, loading, _allPostsMeta }, loadMorePosts }) {
if (loading) {
return <div>Loading</div>
}

const areMorePosts = allPosts.length < _allPostsMeta.count

return (
<section>
<ul>
{allPosts
.sort((x, y) => new Date(y.createdAt) - new Date(x.createdAt))
.map((post, index) =>
<li key={post.id}>
<div>
<span>{index + 1}. </span>
<a href={post.url}>{post.title}</a>
<PostUpvoter id={post.id} votes={post.votes} />
</div>
</li>
)}
</ul>
{areMorePosts ? <button onClick={() => loadMorePosts()}><span />Show More</button> : ''}
<style jsx>{`
section {
padding-bottom: 20px;
}
li {
display: block;
margin-bottom: 10px;
}
div {
align-items: center;
display: flex;
}
a {
font-size: 14px;
margin-right: 10px;
text-decoration: none;
padding-bottom: 0;
border: 0;
}
span {
font-size: 14px;
margin-right: 5px;
}
ul {
margin: 0;
padding: 0;
}
button:before {
align-self: center;
border-style: solid;
border-width: 6px 4px 0 4px;
border-color: #ffffff transparent transparent transparent;
content: "";
height: 0;
width: 0;
}
`}</style>
</section>
)
}

const allPosts = gql`
query allPosts($first: Int!, $skip: Int!) {
allPosts(orderBy: createdAt_DESC, first: $first, skip: $skip) {
id
title
votes
url
createdAt
},
_allPostsMeta {
count
}
}
`

// The `graphql` wrapper executes a GraphQL query and makes the results
// available on the `data` prop of the wrapped component (PostList)
export default graphql(allPosts, {
options: {
variables: {
skip: 0,
first: POSTS_PER_PAGE
}
},
props: ({ data }) => ({
data,
loadMorePosts: () => {
return data.fetchMore({
variables: {
skip: data.allPosts.length
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult.data) {
return previousResult
}
return Object.assign({}, previousResult, {
// Append the new posts results to the old one
allPosts: [...previousResult.allPosts, ...fetchMoreResult.data.allPosts]
})
}
})
}
})
})(PostList)
54 changes: 54 additions & 0 deletions examples/with-apollo/components/PostUpvoter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react'
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'

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
votes
}
}
`

export default graphql(upvotePost, {
props: ({ ownProps, mutate }) => ({
upvote: (id, votes) => mutate({
variables: { id, votes },
optimisticResponse: {
updatePost: {
id: ownProps.id,
votes: ownProps.votes + 1
}
}
})
})
})(PostUpvoter)
79 changes: 79 additions & 0 deletions examples/with-apollo/components/Submit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import gql from 'graphql-tag'
import { graphql } from 'react-apollo'

function Submit ({ createPost }) {
function handleSubmit (e) {
e.preventDefault()

let title = e.target.elements.title.value
let url = e.target.elements.url.value

if (title === '' || url === '') {
window.alert('Both fields are required.')
return false
}

// 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 = ''
}

return (
<form onSubmit={handleSubmit}>
<h1>Submit</h1>
<input placeholder='title' name='title' />
<input placeholder='url' name='url' />
<button type='submit'>Submit</button>
<style jsx>{`
form {
border-bottom: 1px solid #ececec;
padding-bottom: 20px;
margin-bottom: 20px;
}
h1 {
font-size: 20px;
}
input {
display: block;
margin-bottom: 10px;
}
`}</style>
</form>
)
}

const createPost = gql`
mutation createPost($title: String!, $url: String!) {
createPost(title: $title, url: $url) {
id
title
votes
url
createdAt
}
}
`

export default graphql(createPost, {
props: ({ mutate }) => ({
createPost: (title, url) => mutate({
variables: { title, url },
updateQueries: {
allPosts: (previousResult, { mutationResult }) => {
const newPost = mutationResult.data.createPost
return Object.assign({}, previousResult, {
// Append the new post
allPosts: [...previousResult.allPosts, newPost]
})
}
}
})
})
})(Submit)
22 changes: 22 additions & 0 deletions examples/with-apollo/lib/initClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ApolloClient, { createNetworkInterface } from 'apollo-client'

export const initClient = (headers) => {
const client = new ApolloClient({
ssrMode: !process.browser,
headers,
dataIdFromObject: result => result.id || null,
networkInterface: createNetworkInterface({
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn',
opts: {
credentials: 'same-origin'
}
})
})
if (!process.browser) {
return client
}
if (!window.APOLLO_CLIENT) {
window.APOLLO_CLIENT = client
}
return window.APOLLO_CLIENT
}
16 changes: 16 additions & 0 deletions examples/with-apollo/lib/initStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createStore } from 'redux'
import getReducer from './reducer'
import createMiddleware from './middleware'

export const initStore = (client, initialState) => {
let store
if (!process.browser || !window.REDUX_STORE) {
const middleware = createMiddleware(client.middleware())
store = createStore(getReducer(client), initialState, middleware)
if (!process.browser) {
return store
}
window.REDUX_STORE = store
}
return window.REDUX_STORE
}
Loading