diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..e98af08e
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+es
+lib
+build
diff --git a/.eslintrc.js b/.eslintrc.js
index a0cfca0f..5e73ead0 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -34,6 +34,6 @@ module.exports = {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'no-unused-vars': 'error',
- 'linebreak-style': ['error', 'unix'],
+ 'linebreak-style': ['error', 'unix']
}
-};
+}
diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md
index c4e4b686..c45d3571 100644
--- a/.github/ISSUE_TEMPLATE/Bug_Report.md
+++ b/.github/ISSUE_TEMPLATE/Bug_Report.md
@@ -4,7 +4,6 @@ about: Bugs or any unexpected issues.
title: ''
labels: bug
assignees: ''
-
---
### Package
diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md
index 1bf953a5..db6a2935 100644
--- a/.github/ISSUE_TEMPLATE/Feature_Request.md
+++ b/.github/ISSUE_TEMPLATE/Feature_Request.md
@@ -4,7 +4,6 @@ about: I have a suggestion for a new feature!
title: ''
labels: feature
assignees: ''
-
---
### Description
diff --git a/.gitignore b/.gitignore
index 25b10add..60754ef0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ dist
es
packages/graphql-hooks/README.md
packages/*/LICENSE
+build
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..94247d71
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+es
+lib
+build
+CHANGELOG.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c202fe2c..b6ba1058 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,6 +24,8 @@ We use [Lerna](https://lernajs.io) to manage this monorepo, you will find the di
Clone the repository and run `npm install`. This will install the root dependencies and all of the dependencies required by each package, using `lerna bootstrap`.
+If you want to test your changes against an example app then take a look at our [fastify-ssr example](examples/fastify-ssr).
+
All contributions should use the [prettier](https://prettier.io/) formatter, pass linting and pass tests.
You can do this by running:
diff --git a/README.md b/README.md
index 0e7d6edc..3b11c07b 100644
--- a/README.md
+++ b/README.md
@@ -151,9 +151,14 @@ const client = new GraphQLClient(config)
```js
import { ClientContext } from 'graphql-hooks'
-
- {/* children can now consume the client context */}
-
+
+function App() {
+ return (
+
+ {/* children can now consume the client context */}
+
+ )
+}
```
To access the `GraphQLClient` instance, call `React.useContext(ClientContext)`:
diff --git a/examples/fastify-ssr/README.md b/examples/fastify-ssr/README.md
new file mode 100644
index 00000000..bb02e7ef
--- /dev/null
+++ b/examples/fastify-ssr/README.md
@@ -0,0 +1,32 @@
+# GraphQL Hooks Fastify SSR Example
+
+This example uses [Fastify](https://github.com/fastify/fastify) to serve a graphql server and do server side rendering. It's very basic but showcases the different functionality that `graphql-hooks` offers. This example is intended more for local development of the `graphql-hooks` packages.
+
+## How to use
+
+### Running as part of this repo
+
+In the root of this repository run:
+
+```bash
+npm install
+lerna run build
+cd examples/fastify-ssr
+npm run watch
+```
+
+To develop `packages/` with this example locally, you'll need to run `lerna run build` from the root to rebuild files after they've been changed.
+
+### Download the example in isolation:
+
+```bash
+curl https://codeload.github.com/nearform/graphql-hooks/tar.gz/master | tar -xz --strip=2 graphql-hooks-master/examples/fastify-ssr
+cd fastify-ssr
+```
+
+Install it and run:
+
+```bash
+npm install
+npm run watch
+```
diff --git a/examples/fastify-ssr/package.json b/examples/fastify-ssr/package.json
new file mode 100644
index 00000000..3298a4d6
--- /dev/null
+++ b/examples/fastify-ssr/package.json
@@ -0,0 +1,67 @@
+{
+ "name": "fastify-ssr",
+ "version": "1.0.0",
+ "private": true,
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build:client": "webpack",
+ "build:server": "babel src -d lib --copy-files",
+ "prebuild": "rm -rf build lib",
+ "build": "npm run build:server && npm run build:client",
+ "start": "node ./lib/index.js",
+ "watch:client": "webpack --watch",
+ "watch:server": "nodemon --watch src --ignore src/client --exec 'npm run build:server && npm run start | pino-pretty'",
+ "prewatch": "npm run prebuild",
+ "watch": "npm run build:client && npm run watch:server & npm run watch:client"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@reach/router": "^1.2.1",
+ "babel-plugin-dynamic-import-node": "^2.2.0",
+ "fastify": "^1.14.1",
+ "fastify-gql": "^0.8.0",
+ "fastify-static": "^1.1.0",
+ "graphql-hooks": "^3.0.0",
+ "graphql-hooks-memcache": "^1.0.4",
+ "graphql-hooks-ssr": "^1.0.1",
+ "isomorphic-unfetch": "^3.0.0",
+ "react": "16.8.4",
+ "react-dom": "16.8.4",
+ "react-tree-walker": "^4.3.0"
+ },
+ "devDependencies": {
+ "@babel/cli": "^7.2.3",
+ "@babel/core": "^7.2.2",
+ "@babel/preset-env": "^7.3.1",
+ "@babel/preset-react": "^7.0.0",
+ "babel-loader": "^8.0.5",
+ "nodemon": "^1.18.9",
+ "pino-pretty": "^2.5.0",
+ "webpack": "^4.29.1",
+ "webpack-cli": "^3.2.3",
+ "webpack-manifest-plugin": "^2.0.4",
+ "webpack-merge": "^4.2.1"
+ },
+ "browserslist": "> 0.25%, not dead",
+ "babel": {
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "targets": {
+ "node": "current"
+ }
+ }
+ ],
+ "@babel/preset-react"
+ ],
+ "plugins": [
+ "@babel/plugin-proposal-object-rest-spread",
+ "dynamic-import-node"
+ ]
+ }
+}
diff --git a/examples/fastify-ssr/src/app/AppShell.js b/examples/fastify-ssr/src/app/AppShell.js
new file mode 100644
index 00000000..de1f335b
--- /dev/null
+++ b/examples/fastify-ssr/src/app/AppShell.js
@@ -0,0 +1,30 @@
+import React from 'react'
+import { Link, Router } from '@reach/router'
+
+// components
+import NotFoundPage from './pages/NotFoundPage'
+import HomePage from './pages/HomePage'
+import PaginationPage from './pages/PaginationPage'
+
+class AppShell extends React.Component {
+ render() {
+ return (
+
+
GraphQL Hooks
+
+
+
+
+
+
+
+ )
+ }
+}
+
+AppShell.propTypes = {}
+
+export default AppShell
diff --git a/examples/fastify-ssr/src/app/components/Hello.js b/examples/fastify-ssr/src/app/components/Hello.js
new file mode 100644
index 00000000..069fcd53
--- /dev/null
+++ b/examples/fastify-ssr/src/app/components/Hello.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { useQuery } from 'graphql-hooks'
+
+const HELLO_QUERY = `
+ query Hello($name: String) {
+ hello(name: $name)
+ }
+`
+
+function HelloComponent({ user }) {
+ const { loading, error, data } = useQuery(HELLO_QUERY, {
+ variables: { name: user.name }
+ })
+
+ if (loading) return 'loading HelloComponent...'
+ if (error) return 'error HelloComponent'
+
+ return {data.hello}
+}
+
+HelloComponent.propTypes = {
+ user: PropTypes.shape({
+ name: PropTypes.string
+ })
+}
+
+export default HelloComponent
diff --git a/examples/fastify-ssr/src/app/pages/HomePage.js b/examples/fastify-ssr/src/app/pages/HomePage.js
new file mode 100644
index 00000000..1504ce8a
--- /dev/null
+++ b/examples/fastify-ssr/src/app/pages/HomePage.js
@@ -0,0 +1,82 @@
+import React, { Fragment } from 'react'
+
+import { useQuery, useManualQuery, useMutation } from 'graphql-hooks'
+
+// components
+import HelloComponent from '../components/Hello'
+
+const HOMEPAGE_QUERY = `
+ query HomepageQuery {
+ users {
+ name
+ }
+ }
+`
+
+const GET_FIRST_USER_QUERY = `
+ query FirstUser {
+ firstUser {
+ name
+ }
+ }
+`
+
+const CREATE_USER_MUTATION = `
+ mutation CreateUser($name: String!) {
+ createUser(name: $name) {
+ name
+ }
+ }
+`
+
+function HomePage() {
+ const [name, setName] = React.useState('')
+ const { loading, data, error, refetch: refetchUsers } = useQuery(
+ HOMEPAGE_QUERY
+ )
+ const [createUser] = useMutation(CREATE_USER_MUTATION)
+
+ const [getFirstUser, { data: firstUserData }] = useManualQuery(
+ GET_FIRST_USER_QUERY
+ )
+
+ async function createNewUser() {
+ await createUser({ variables: { name } })
+ setName('')
+ refetchUsers()
+ }
+
+ return (
+
+ Home page
+ {loading &&
...loading
}
+ {error &&
error occured
}
+ {!loading && !error && data.users && (
+
+ List of users:
+ {data.users.length === 0 && No users found}
+ {!!data.users.length && (
+
+ {data.users.map((user, i) => (
+ - {user.name}
+ ))}
+
+ )}
+
+
+ )}
+
+ setName(e.target.value)}
+ />
+
+
+
+
First User: {firstUserData && firstUserData.firstUser.name}
+
+ )
+}
+
+export default HomePage
diff --git a/examples/fastify-ssr/src/app/pages/NotFoundPage.js b/examples/fastify-ssr/src/app/pages/NotFoundPage.js
new file mode 100644
index 00000000..f8b89857
--- /dev/null
+++ b/examples/fastify-ssr/src/app/pages/NotFoundPage.js
@@ -0,0 +1,7 @@
+import React from 'react'
+
+function NotFoundPage() {
+ return 404 - Not Found
+}
+
+export default NotFoundPage
diff --git a/examples/fastify-ssr/src/app/pages/PaginationPage.js b/examples/fastify-ssr/src/app/pages/PaginationPage.js
new file mode 100644
index 00000000..5a107588
--- /dev/null
+++ b/examples/fastify-ssr/src/app/pages/PaginationPage.js
@@ -0,0 +1,36 @@
+import React from 'react'
+
+import { useQuery } from 'graphql-hooks'
+
+const USERS_QUERY = `
+ query UsersQuery($skip: Int, $limit: Int) {
+ users(skip: $skip, limit: $limit) {
+ name
+ }
+ }
+`
+
+function PaginationPage() {
+ const [page, setPage] = React.useState(1)
+ const { data } = useQuery(USERS_QUERY, {
+ variables: {
+ limit: 1,
+ skip: page - 1
+ }
+ })
+
+ return (
+
+ User Pagination Users:
+
+ {data &&
+ data.users &&
+ data.users.map((user, i) => - {user.name}
)}
+
+
+
+
+ )
+}
+
+export default PaginationPage
diff --git a/examples/fastify-ssr/src/client/js/app-shell.js b/examples/fastify-ssr/src/client/js/app-shell.js
new file mode 100644
index 00000000..76ecaf5d
--- /dev/null
+++ b/examples/fastify-ssr/src/client/js/app-shell.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import { hydrate } from 'react-dom'
+
+import AppShell from '../../app/AppShell'
+
+// graphql-hooks
+import { GraphQLClient, ClientContext } from 'graphql-hooks'
+import memCache from 'graphql-hooks-memcache'
+
+const initialState = window.__INITIAL_STATE__
+const client = new GraphQLClient({
+ url: '/graphql',
+ cache: memCache({ initialState })
+})
+
+const App = (
+
+
+
+)
+
+hydrate(App, document.getElementById('app-root'))
diff --git a/examples/fastify-ssr/src/index.js b/examples/fastify-ssr/src/index.js
new file mode 100644
index 00000000..6a926796
--- /dev/null
+++ b/examples/fastify-ssr/src/index.js
@@ -0,0 +1 @@
+require('./server')()
diff --git a/examples/fastify-ssr/src/server/graphql/index.js b/examples/fastify-ssr/src/server/graphql/index.js
new file mode 100644
index 00000000..b7897841
--- /dev/null
+++ b/examples/fastify-ssr/src/server/graphql/index.js
@@ -0,0 +1,18 @@
+const fastifyGQL = require('fastify-gql')
+
+const schema = require('./schema')
+const resolvers = require('./resolvers')
+
+function registerGraphQL(fastify, opts, next) {
+ fastify.register(fastifyGQL, {
+ schema,
+ resolvers,
+ graphiql: true
+ })
+
+ next()
+}
+
+registerGraphQL[Symbol.for('skip-override')] = true
+
+module.exports = registerGraphQL
diff --git a/examples/fastify-ssr/src/server/graphql/resolvers.js b/examples/fastify-ssr/src/server/graphql/resolvers.js
new file mode 100644
index 00000000..f60c20ba
--- /dev/null
+++ b/examples/fastify-ssr/src/server/graphql/resolvers.js
@@ -0,0 +1,28 @@
+const users = [
+ {
+ name: 'Brian'
+ },
+ {
+ name: 'Jack'
+ },
+ {
+ name: 'Joe'
+ }
+]
+
+module.exports = {
+ Query: {
+ users: (_, { skip = 0, limit }) => {
+ const end = limit ? skip + limit : undefined
+ return users.slice(skip, end)
+ },
+ firstUser: () => users[0],
+ hello: (_, { name }) => `Hello ${name}`
+ },
+ Mutation: {
+ createUser: (_, user) => {
+ users.push(user)
+ return user
+ }
+ }
+}
diff --git a/examples/fastify-ssr/src/server/graphql/schema.js b/examples/fastify-ssr/src/server/graphql/schema.js
new file mode 100644
index 00000000..dcbffb0c
--- /dev/null
+++ b/examples/fastify-ssr/src/server/graphql/schema.js
@@ -0,0 +1,15 @@
+module.exports = `
+ type User {
+ name: String
+ }
+
+ type Query {
+ users(skip: Int, limit: Int): [User]
+ firstUser: User
+ hello(name: String): String
+ }
+
+ type Mutation {
+ createUser(name: String!): User
+ }
+`
diff --git a/examples/fastify-ssr/src/server/handlers/app-shell.js b/examples/fastify-ssr/src/server/handlers/app-shell.js
new file mode 100644
index 00000000..48592dfb
--- /dev/null
+++ b/examples/fastify-ssr/src/server/handlers/app-shell.js
@@ -0,0 +1,73 @@
+const React = require('react')
+const ReactDOMServer = require('react-dom/server')
+const { ServerLocation } = require('@reach/router')
+
+// graphql-hooks
+const { getInitialState } = require('graphql-hooks-ssr')
+const { GraphQLClient, ClientContext } = require('graphql-hooks')
+const memCache = require('graphql-hooks-memcache')
+
+// components
+const { default: AppShell } = require('../../app/AppShell')
+
+// helpers
+const { getBundlePath } = require('../helpers/manifest')
+
+function renderHead() {
+ return `
+
+ Hello World!
+
+ `
+}
+
+async function renderScripts({ initialState }) {
+ const appShellBundlePath = await getBundlePath('app-shell.js')
+ return `
+
+
+ `
+}
+
+async function appShellHandler(req, reply) {
+ const head = renderHead()
+
+ const client = new GraphQLClient({
+ url: 'http://127.0.0.1:3000/graphql',
+ cache: memCache(),
+ fetch: require('isomorphic-unfetch'),
+ logErrors: true
+ })
+
+ const App = (
+
+
+
+
+
+ )
+
+ const initialState = await getInitialState({ App, client })
+ const content = ReactDOMServer.renderToString(App)
+ const scripts = await renderScripts({ initialState })
+
+ const html = `
+
+
+ ${head}
+
+ ${content}
+ ${scripts}
+
+
+ `
+
+ reply.type('text/html').send(html)
+}
+
+module.exports = appShellHandler
diff --git a/examples/fastify-ssr/src/server/helpers/manifest.js b/examples/fastify-ssr/src/server/helpers/manifest.js
new file mode 100644
index 00000000..a7a34a9b
--- /dev/null
+++ b/examples/fastify-ssr/src/server/helpers/manifest.js
@@ -0,0 +1,37 @@
+const fs = require('fs')
+const path = require('path')
+const { promisify } = require('util')
+
+const readFileAsync = promisify(fs.readFile)
+
+const manifestPath = path.join(process.cwd(), 'build/public/js/manifest.json')
+let cachedManifest
+
+getManifest()
+
+async function getManifest() {
+ if (cachedManifest) {
+ return cachedManifest
+ }
+
+ try {
+ cachedManifest = JSON.parse(await readFileAsync(manifestPath))
+ return cachedManifest
+ } catch (error) {
+ if (error.code !== 'ENOENT') {
+ throw error
+ }
+
+ return {}
+ }
+}
+
+async function getBundlePath(manifestKey) {
+ const manifest = await getManifest()
+ return manifest[manifestKey] || manifestKey
+}
+
+module.exports = {
+ getManifest,
+ getBundlePath
+}
diff --git a/examples/fastify-ssr/src/server/index.js b/examples/fastify-ssr/src/server/index.js
new file mode 100644
index 00000000..0ff9c12f
--- /dev/null
+++ b/examples/fastify-ssr/src/server/index.js
@@ -0,0 +1,25 @@
+const path = require('path')
+const fastify = require('fastify')
+
+// plugins
+const graphqlPlugin = require('./graphql')
+
+// handlers
+const appShellHandler = require('./handlers/app-shell')
+
+module.exports = () => {
+ const app = fastify({
+ logger: true
+ })
+
+ app.register(require('fastify-static'), {
+ root: path.join(process.cwd(), 'build/public')
+ })
+
+ app.register(graphqlPlugin)
+
+ app.get('/', appShellHandler)
+ app.get('/users', appShellHandler)
+
+ app.listen(3000)
+}
diff --git a/examples/fastify-ssr/webpack.config.js b/examples/fastify-ssr/webpack.config.js
new file mode 100644
index 00000000..2da01f64
--- /dev/null
+++ b/examples/fastify-ssr/webpack.config.js
@@ -0,0 +1,80 @@
+const path = require('path')
+const merge = require('webpack-merge')
+const ManifestPlugin = require('webpack-manifest-plugin')
+
+const PATHS = {
+ build: path.join(__dirname, 'build'),
+ src: path.join(__dirname, 'src'),
+ node_modules: path.join(__dirname, 'node_modules')
+}
+
+const commonConfig = {
+ entry: {
+ 'app-shell': path.join(PATHS.src, 'client/js/app-shell.js')
+ },
+ devtool: '#cheap-module-source-map',
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ loader: 'babel-loader',
+ options: {
+ babelrc: false,
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: '> 5%, not dead'
+ }
+ ],
+ '@babel/preset-react'
+ ],
+ plugins: [
+ '@babel/plugin-proposal-object-rest-spread',
+ 'dynamic-import-node'
+ ]
+ }
+ }
+ ]
+ },
+ resolve: {
+ extensions: ['.js', '.jsx', '.json'],
+ modules: [PATHS.src, 'node_modules'],
+ symlinks: false
+ },
+ output: {
+ filename: '[name].js',
+ chunkFilename: '[name].js',
+ publicPath: '/js/',
+ path: path.join(PATHS.build, 'public/js')
+ },
+ plugins: [new ManifestPlugin()]
+}
+
+const productionConfig = {
+ mode: 'production',
+ optimization: {
+ minimize: false
+ },
+ output: {
+ filename: '[chunkhash].[name].js',
+ chunkFilename: '[chunkhash].[name].js',
+ publicPath: '/js/',
+ path: path.join(PATHS.build, 'public/js')
+ }
+}
+
+const developmentConfig = {
+ mode: 'development'
+}
+
+module.exports = () => {
+ if (
+ process.env.NODE_ENV === 'production' ||
+ process.env.NODE_ENV === 'staging'
+ ) {
+ return merge(commonConfig, productionConfig)
+ }
+
+ return merge(commonConfig, developmentConfig)
+}
diff --git a/lerna.json b/lerna.json
index 84a85206..f850e601 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "packages": ["packages/*"],
+ "packages": ["packages/*", "examples/*"],
"version": "independent",
"command": {
"bootstrap": {
@@ -11,6 +11,9 @@
},
"version": {
"conventionalCommits": true
+ },
+ "bootstrap": {
+ "hoist": ["react", "react-dom"]
}
}
}
diff --git a/package.json b/package.json
index cb8af8ad..4e5cd525 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,8 @@
"test:coverage": "jest --coverage",
"postinstall": "lerna bootstrap --no-ci",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
- "eslint": "eslint *.js packages/*/*.js packages/*/{src,test}/**/*.js",
- "prettier": "prettier *.js *.md packages/*/*.{js,md} packages/*/{src,test}/**/*.{js,md} --write",
+ "eslint": "eslint '**/*.js'",
+ "prettier": "prettier '**/*.js' '**/*.md' --write",
"release": "lerna publish"
},
"devDependencies": {
@@ -35,17 +35,7 @@
"rollup-plugin-terser": "4.0.4"
},
"lint-staged": {
- "./packages/*/{src,test}/**/*.js": [
- "eslint",
- "prettier --write",
- "git add"
- ],
- "./packages/*/*.js": [
- "eslint",
- "prettier --write",
- "git add"
- ],
- "*.js": [
+ "**/*.js": [
"eslint",
"prettier --write",
"git add"