From 5fd7b85280c1a17e4d32350e1c302516945aab92 Mon Sep 17 00:00:00 2001 From: Joe Warren Date: Fri, 1 Mar 2019 17:21:03 +0000 Subject: [PATCH] Add graphql-hooks example (#6482) Adds an example app using [graphql-hooks](https://github.com/nearform/graphql-hooks) that started life as the with-apollo example app. It uses the same graph.cool backend, mostly to demonstrate how similar it is. --- examples/with-graphql-hooks/.nowignore | 1 + examples/with-graphql-hooks/README.md | 52 ++++++++ examples/with-graphql-hooks/components/app.js | 42 +++++++ .../components/error-message.js | 13 ++ .../with-graphql-hooks/components/header.js | 28 +++++ .../components/post-list.js | 112 ++++++++++++++++++ .../components/post-upvoter.js | 57 +++++++++ .../with-graphql-hooks/components/submit.js | 56 +++++++++ .../with-graphql-hooks/lib/init-graphql.js | 29 +++++ .../lib/with-graphql-client.js | 62 ++++++++++ examples/with-graphql-hooks/next.config.js | 3 + examples/with-graphql-hooks/now.json | 5 + examples/with-graphql-hooks/package.json | 24 ++++ examples/with-graphql-hooks/pages/_app.js | 19 +++ examples/with-graphql-hooks/pages/about.js | 30 +++++ examples/with-graphql-hooks/pages/index.js | 10 ++ 16 files changed, 543 insertions(+) create mode 100644 examples/with-graphql-hooks/.nowignore create mode 100644 examples/with-graphql-hooks/README.md create mode 100644 examples/with-graphql-hooks/components/app.js create mode 100644 examples/with-graphql-hooks/components/error-message.js create mode 100644 examples/with-graphql-hooks/components/header.js create mode 100644 examples/with-graphql-hooks/components/post-list.js create mode 100644 examples/with-graphql-hooks/components/post-upvoter.js create mode 100644 examples/with-graphql-hooks/components/submit.js create mode 100644 examples/with-graphql-hooks/lib/init-graphql.js create mode 100644 examples/with-graphql-hooks/lib/with-graphql-client.js create mode 100644 examples/with-graphql-hooks/next.config.js create mode 100644 examples/with-graphql-hooks/now.json create mode 100644 examples/with-graphql-hooks/package.json create mode 100644 examples/with-graphql-hooks/pages/_app.js create mode 100644 examples/with-graphql-hooks/pages/about.js create mode 100644 examples/with-graphql-hooks/pages/index.js diff --git a/examples/with-graphql-hooks/.nowignore b/examples/with-graphql-hooks/.nowignore new file mode 100644 index 0000000000..b512c09d47 --- /dev/null +++ b/examples/with-graphql-hooks/.nowignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/examples/with-graphql-hooks/README.md b/examples/with-graphql-hooks/README.md new file mode 100644 index 0000000000..6380cd3013 --- /dev/null +++ b/examples/with-graphql-hooks/README.md @@ -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. diff --git a/examples/with-graphql-hooks/components/app.js b/examples/with-graphql-hooks/components/app.js new file mode 100644 index 0000000000..ec15c11f56 --- /dev/null +++ b/examples/with-graphql-hooks/components/app.js @@ -0,0 +1,42 @@ +export default ({ children }) => ( +
+ {children} + +
+) diff --git a/examples/with-graphql-hooks/components/error-message.js b/examples/with-graphql-hooks/components/error-message.js new file mode 100644 index 0000000000..8497a5d2df --- /dev/null +++ b/examples/with-graphql-hooks/components/error-message.js @@ -0,0 +1,13 @@ +export default ({ message }) => ( + +) diff --git a/examples/with-graphql-hooks/components/header.js b/examples/with-graphql-hooks/components/header.js new file mode 100644 index 0000000000..392b7d962f --- /dev/null +++ b/examples/with-graphql-hooks/components/header.js @@ -0,0 +1,28 @@ +import Link from 'next/link' +import { withRouter } from 'next/router' + +const Header = ({ router: { pathname } }) => ( +
+ + Home + + + About + + +
+) + +export default withRouter(Header) diff --git a/examples/with-graphql-hooks/components/post-list.js b/examples/with-graphql-hooks/components/post-list.js new file mode 100644 index 0000000000..64e9b521fc --- /dev/null +++ b/examples/with-graphql-hooks/components/post-list.js @@ -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 + if (!data) return
Loading
+ + const { allPosts, _allPostsMeta } = data + + const areMorePosts = allPosts.length < _allPostsMeta.count + return ( + + { + refetch({ variables: { skip: 0, first: allPosts.length } }) + }} + /> +
+
    + {allPosts.map((post, index) => ( +
  • +
    + {index + 1}. + {post.title} + { + refetch({ variables: { skip: 0, first: allPosts.length } }) + }} + /> +
    +
  • + ))} +
+ {areMorePosts ? ( + + ) : ( + '' + )} + +
+
+ ) +} diff --git a/examples/with-graphql-hooks/components/post-upvoter.js b/examples/with-graphql-hooks/components/post-upvoter.js new file mode 100644 index 0000000000..bc9fd00a1c --- /dev/null +++ b/examples/with-graphql-hooks/components/post-upvoter.js @@ -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 ( + + ) +} diff --git a/examples/with-graphql-hooks/components/submit.js b/examples/with-graphql-hooks/components/submit.js new file mode 100644 index 0000000000..b3b9894800 --- /dev/null +++ b/examples/with-graphql-hooks/components/submit.js @@ -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 ( +
handleSubmit(event, onSubmission, createPost)}> +

Submit

+ + + + +
+ ) +} + +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) +} diff --git a/examples/with-graphql-hooks/lib/init-graphql.js b/examples/with-graphql-hooks/lib/init-graphql.js new file mode 100644 index 0000000000..c3041cad28 --- /dev/null +++ b/examples/with-graphql-hooks/lib/init-graphql.js @@ -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 +} diff --git a/examples/with-graphql-hooks/lib/with-graphql-client.js b/examples/with-graphql-hooks/lib/with-graphql-client.js new file mode 100644 index 0000000000..e58e894042 --- /dev/null +++ b/examples/with-graphql-hooks/lib/with-graphql-client.js @@ -0,0 +1,62 @@ +import React from 'react' +import initGraphQL from './init-graphql' +import Head from 'next/head' +import { getInitialState } from 'graphql-hooks-ssr' + +export default App => { + return class GraphQLHooks extends React.Component { + static displayName = 'GraphQLHooks(App)' + static async getInitialProps (ctx) { + const { Component, router } = ctx + + let appProps = {} + if (App.getInitialProps) { + appProps = await App.getInitialProps(ctx) + } + + // Run all GraphQL queries in the component tree + // and extract the resulting data + const graphQLClient = initGraphQL() + let graphQLState = {} + if (!process.browser) { + try { + // Run all GraphQL queries + graphQLState = await getInitialState({ + App: ( + + ), + client: graphQLClient + }) + } catch (error) { + // Prevent GraphQL hooks client errors from crashing SSR. + // Handle them in components via the state.error prop: + // https://github.com/nearform/graphql-hooks#usequery + console.error('Error while running `getInitialState`', error) + } + + // getInitialState does not call componentWillUnmount + // head side effect therefore need to be cleared manually + Head.rewind() + } + + return { + ...appProps, + graphQLState + } + } + + constructor (props) { + super(props) + this.graphQLClient = initGraphQL(props.graphQLState) + } + + render () { + return + } + } +} diff --git a/examples/with-graphql-hooks/next.config.js b/examples/with-graphql-hooks/next.config.js new file mode 100644 index 0000000000..0fbd0e535c --- /dev/null +++ b/examples/with-graphql-hooks/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + target: 'serverless' +} diff --git a/examples/with-graphql-hooks/now.json b/examples/with-graphql-hooks/now.json new file mode 100644 index 0000000000..7a22023c12 --- /dev/null +++ b/examples/with-graphql-hooks/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "name": "next-with-graphql-hooks", + "builds": [{ "src": "next.config.js", "use": "@now/next" }] +} diff --git a/examples/with-graphql-hooks/package.json b/examples/with-graphql-hooks/package.json new file mode 100644 index 0000000000..4d7d4e0f60 --- /dev/null +++ b/examples/with-graphql-hooks/package.json @@ -0,0 +1,24 @@ +{ + "name": "with-graphql-hooks", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "now-build": "next build" + }, + "author": "", + "license": "ISC", + "dependencies": { + "graphql-hooks": "^3.2.1", + "graphql-hooks-memcache": "^1.0.4", + "graphql-hooks-ssr": "^1.0.1", + "isomorphic-unfetch": "^3.0.0", + "next": "^8.0.1", + "prop-types": "^15.7.2", + "react": "^16.8.2", + "react-dom": "^16.8.2" + } +} diff --git a/examples/with-graphql-hooks/pages/_app.js b/examples/with-graphql-hooks/pages/_app.js new file mode 100644 index 0000000000..cf8846a549 --- /dev/null +++ b/examples/with-graphql-hooks/pages/_app.js @@ -0,0 +1,19 @@ +import App, { Container } from 'next/app' +import React from 'react' +import withGraphQLClient from '../lib/with-graphql-client' +import { ClientContext } from 'graphql-hooks' + +class MyApp extends App { + render () { + const { Component, pageProps, graphQLClient } = this.props + return ( + + + + + + ) + } +} + +export default withGraphQLClient(MyApp) diff --git a/examples/with-graphql-hooks/pages/about.js b/examples/with-graphql-hooks/pages/about.js new file mode 100644 index 0000000000..88970a29f5 --- /dev/null +++ b/examples/with-graphql-hooks/pages/about.js @@ -0,0 +1,30 @@ +import App from '../components/app' +import Header from '../components/header' + +export default () => ( + +
+
+

The Idea Behind This Example

+

+ GraphQL Hooks is + a library from NearForm that intends to be a minimal hooks-first GraphQL + client. Providing it in a way familiar to Apollo users. +

+ +

+ 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. +

+ +

+ You'll see this shares the same{' '} + 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. +

+
+ +) diff --git a/examples/with-graphql-hooks/pages/index.js b/examples/with-graphql-hooks/pages/index.js new file mode 100644 index 0000000000..22a7b48c7c --- /dev/null +++ b/examples/with-graphql-hooks/pages/index.js @@ -0,0 +1,10 @@ +import App from '../components/app' +import Header from '../components/header' +import PostList from '../components/post-list' + +export default () => ( + +
+ + +)