Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add graphql-hooks example #6482

Merged
merged 2 commits into from
Mar 1, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/with-graphql-hooks/.nowignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
52 changes: 52 additions & 0 deletions examples/with-graphql-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-graphql-hooks)

# GraphQL Hooks Example

This started life as a copy of the `with-apollo` example. We then stripped out Apollo and replaced it with `graphql-hooks`. This was mostly as an exercise in ensuring basic functionality could be achieved in a similar way to Apollo. The [bundle size](https://bundlephobia.com/result?p=graphql-hooks@3.2.1) of `graphql-hooks` is tiny in comparison to Apollo and should cover a fair amount of use cases.

## Demo

https://next-with-graphql-hooks.now.sh

## How to use

### Using `create-next-app`

Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:

```bash
npx create-next-app --example with-graphql-hooks with-graphql-hooks-app
# or
yarn create next-app --example with-graphql-hooks with-graphql-hooks-app
```

### Download manually

Download the example:

```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-graphql-hooks
cd with-graphql-hooks
```

Install it and run:

```bash
npm install
npm run dev
# or
yarn
yarn dev
```

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

```bash
now
```

## The idea behind the example

[GraphQL Hooks](https://github.com/nearform/graphql-hooks) is a library from NearForm that intends to be a minimal hooks-first GraphQL client. Providing a similar API to Apollo.

You'll see this shares the same [graph.cool](https://www.graph.cool) backend as the Apollo example, this is so you can compare the two side by side. The app itself should also look identical.
42 changes: 42 additions & 0 deletions examples/with-graphql-hooks/components/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 0.3s;
}
button:focus {
outline: none;
}
`}</style>
</main>
)
13 changes: 13 additions & 0 deletions examples/with-graphql-hooks/components/error-message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default ({ message }) => (
<aside>
{message}
<style jsx>{`
aside {
padding: 1.5em;
font-size: 14px;
color: white;
background-color: red;
}
`}</style>
</aside>
)
28 changes: 28 additions & 0 deletions examples/with-graphql-hooks/components/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Link from 'next/link'
import { withRouter } from 'next/router'

const Header = ({ router: { pathname } }) => (
<header>
<Link prefetch href='/'>
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
</Link>
<Link prefetch 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>
)

export default withRouter(Header)
112 changes: 112 additions & 0 deletions examples/with-graphql-hooks/components/post-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { Fragment, useState } from 'react'
import { useQuery } from 'graphql-hooks'
import ErrorMessage from './error-message'
import PostUpvoter from './post-upvoter'
import Submit from './submit'

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

export default function PostList () {
const [skip, setSkip] = useState(0)
const { loading, error, data, refetch } = useQuery(allPostsQuery, {
variables: { skip, first: 10 },
updateData: (prevResult, result) => ({
...result,
allPosts: [...prevResult.allPosts, ...result.allPosts]
})
})

if (error) return <ErrorMessage message='Error loading posts.' />
if (!data) return <div>Loading</div>

const { allPosts, _allPostsMeta } = data

const areMorePosts = allPosts.length < _allPostsMeta.count
return (
<Fragment>
<Submit
onSubmission={() => {
refetch({ variables: { skip: 0, first: allPosts.length } })
}}
/>
<section>
<ul>
{allPosts.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}
onUpdate={() => {
refetch({ variables: { skip: 0, first: allPosts.length } })
}}
/>
</div>
</li>
))}
</ul>
{areMorePosts ? (
<button onClick={() => setSkip(skip + 10)}>
{' '}
{loading && !data ? 'Loading...' : '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;
margin-right: 5px;
width: 0;
}
`}</style>
</section>
</Fragment>
)
}
57 changes: 57 additions & 0 deletions examples/with-graphql-hooks/components/post-upvoter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { useMutation } from 'graphql-hooks'

const UPDATE_POST = `
mutation updatePost($id: ID!, $votes: Int) {
updatePost(id: $id, votes: $votes) {
id
__typename
votes
}
}
`

export default function PostUpvoter ({ votes, id, onUpdate }) {
const [updatePost] = useMutation(UPDATE_POST)

return (
<button
onClick={async () => {
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
try {
const result = await updatePost({
variables: {
id,
votes: votes + 1
}
})

onUpdate && onUpdate(result)
} catch (e) {
console.error('error upvoting post', e)
}
}}
>
{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>
)
}
56 changes: 56 additions & 0 deletions examples/with-graphql-hooks/components/submit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react'
import { useMutation } from 'graphql-hooks'

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

export default function Submit ({ onSubmission }) {
const [createPost, state] = useMutation(CREATE_POST)

return (
<form onSubmit={event => handleSubmit(event, onSubmission, createPost)}>
<h1>Submit</h1>
<input placeholder='title' name='title' type='text' required />
<input placeholder='url' name='url' type='url' required />
<button type='submit'>{state.loading ? 'Loading...' : '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>
)
}

async function handleSubmit (event, onSubmission, createPost) {
event.preventDefault()
const form = event.target
const formData = new window.FormData(form)
const title = formData.get('title')
const url = formData.get('url')
form.reset()
const result = await createPost({
variables: {
title,
url
}
})
onSubmission && onSubmission(result)
}
29 changes: 29 additions & 0 deletions examples/with-graphql-hooks/lib/init-graphql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GraphQLClient } from 'graphql-hooks'
import memCache from 'graphql-hooks-memcache'
import unfetch from 'isomorphic-unfetch'

let graphQLClient = null

function create (initialState = {}) {
return new GraphQLClient({
ssrMode: !process.browser,
url: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn',
cache: memCache({ initialState }),
fetch: process.browser ? fetch.bind() : unfetch // eslint-disable-line
})
}

export default function initGraphQL (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) {
return create(initialState)
}

// Reuse client on the client-side
if (!graphQLClient) {
graphQLClient = create(initialState)
}

return graphQLClient
}