Skip to content

Commit 02261fe

Browse files
authored
feat: add support for GQL document input (#1151)
* feat: add support for GQL document input * chore: update documentation * chore: correct util response * chore: correct peer dependency * chore: address comments * chore: correct type dependency
1 parent fe18ee9 commit 02261fe

File tree

20 files changed

+753
-170
lines changed

20 files changed

+753
-170
lines changed

README.md

Lines changed: 112 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ If you need a client that offers more customization such as advanced cache confi
131131
- [Other]
132132
- [Request interceptors](#request-interceptors)
133133
- [AbortController](#abortController)
134+
- [GraphQL document support](#graphql-document-support)
134135

135136
## API
136137

@@ -239,7 +240,7 @@ function MyComponent() {
239240

240241
This is a custom hook that takes care of fetching your query and storing the result in the cache. It won't refetch the query unless `query` or `options.variables` changes.
241242

242-
- `query`: Your GraphQL query as a plain string
243+
- `query`: Your GraphQL query as a plain string or DocumentNode
243244
- `options`: Object with the following optional properties
244245
- `variables`: Object e.g. `{ limit: 10 }`
245246
- `operationName`: If your query has multiple operations, pass the name of the operation you wish to execute.
@@ -279,7 +280,7 @@ const { loading, error, data, refetch, cacheHit } = useQuery(QUERY)
279280

280281
## `useManualQuery`
281282

282-
Use this when you don't want a query to automatically be fetched, or wish to call a query programmatically.
283+
Use this when you don't want a query to automatically be fetched or wish to call a query programmatically.
283284

284285
**Usage**:
285286

@@ -416,7 +417,7 @@ To use subscription you can use either [subscriptions-transport-ws](https://gith
416417

417418
`useSubscription(operation, callback)`
418419

419-
- `operation`: Object - The GraphQL operation the following properties:
420+
- `operation`: Object - The GraphQL operation has the following properties:
420421
- `query`: String (required) - the GraphQL query
421422
- `variables`: Object (optional) - Any variables the query might need
422423
- `operationName`: String (optional) - If your query has multiple operations, you can choose which operation you want to call.
@@ -425,7 +426,7 @@ To use subscription you can use either [subscriptions-transport-ws](https://gith
425426

426427
**Usage**:
427428

428-
First follow the [quick start guide](#Quick-Start) to create the client and povider. Then we need to update the config for our `GraphQLClient` passing in the `subscriptionClient`:
429+
First, follow the [quick start guide](#Quick-Start) to create the client and provider. Then we need to update the config for our `GraphQLClient` passing in the `subscriptionClient`:
429430

430431
```js
431432
import { GraphQLClient } from 'graphql-hooks'
@@ -1255,6 +1256,91 @@ it('shows "No posts" if 0 posts are returned', async () => {
12551256
})
12561257
```
12571258

1259+
## Typescript Support
1260+
1261+
All client methods support the ability to provide type information for response data, query variables and error responses.
1262+
1263+
```typescript
1264+
import { useQuery } from 'graphql-hooks'
1265+
1266+
type User = {
1267+
id: string
1268+
name: string
1269+
}
1270+
1271+
type CustomError = {
1272+
message: string
1273+
extensions?: Record<string, any>
1274+
}
1275+
1276+
const HOMEPAGE_QUERY = `query HomePage($limit: Int) {
1277+
users(limit: $limit) {
1278+
id
1279+
name
1280+
}
1281+
}`
1282+
1283+
function MyComponent() {
1284+
const { loading, error, data } = useQuery<
1285+
User,
1286+
{ limit: number },
1287+
CustomError
1288+
>(HOMEPAGE_QUERY, {
1289+
variables: {
1290+
limit: 10
1291+
}
1292+
})
1293+
1294+
if (loading) return 'Loading...'
1295+
if (error) return 'Something Bad Happened'
1296+
1297+
return (
1298+
<ul>
1299+
{data.users.map(({ id, name }) => (
1300+
<li key={id}>{name}</li>
1301+
))}
1302+
</ul>
1303+
)
1304+
}
1305+
```
1306+
1307+
`graphql-hooks` also supports `TypedDocumentNode`. This allows you to use GraphQL code gen to create `DocumentNode`s for your GQL queries and receive full type support.
1308+
1309+
```typescript
1310+
import { useQuery } from 'graphql-hooks'
1311+
import { graphql } from './gql'
1312+
1313+
const HOMEPAGE_QUERY = graphql(`query HomePage($limit: Int) {
1314+
users(limit: $limit) {
1315+
id
1316+
name
1317+
}
1318+
}`)
1319+
1320+
function MyComponent() {
1321+
// data will be typed as User objects with id, name properties
1322+
const { loading, error, data } = useQuery(HOMEPAGE_QUERY, {
1323+
variables: {
1324+
limit: 10
1325+
}
1326+
})
1327+
1328+
if (loading) return 'Loading...'
1329+
if (error) return 'Something Bad Happened'
1330+
1331+
return (
1332+
<ul>
1333+
{data.users.map(({ id, name }) => (
1334+
<li key={id}>{name}</li>
1335+
))}
1336+
</ul>
1337+
)
1338+
}
1339+
```
1340+
1341+
Full details of the features of `TypedDocumentNode` and GraphQL Code Generator can be found [here](https://the-guild.dev/graphql/codegen). Full examples of this implementation are in the examples folder.
1342+
1343+
12581344
## Other
12591345

12601346
### Request interceptors
@@ -1316,52 +1402,38 @@ function AbortControllerExample() {
13161402
}
13171403
```
13181404
1319-
## Typescript Support
1405+
### GraphQL Document Support
13201406
1321-
All client methods support the ability to provide type information for response data, query variables and error responses.
1407+
As well as supporting input of your queries as strings, this library also supports using a `DocumentNode`. Document nodes can be generated using a code-generation tool such as [GraphQL codegen](https://the-guild.dev/graphql/codegen) which will provide typing information for your queries based on your GraphQL schema (see the typescript example). If you don't want to use a code-generation library you can use `graphql-tag` to generate a `DocumentNode`.
13221408
1323-
```typescript
1324-
import { useQuery } from 'graphql-hooks'
13251409
1326-
type User = {
1327-
id: string
1328-
name: string
1329-
}
1330-
1331-
type CustomError = {
1332-
message: string
1333-
extensions?: Record<string, any>
1334-
}
1410+
```js
1411+
import gql from 'graphql-tag'
13351412

1336-
const HOMEPAGE_QUERY = `query HomePage($limit: Int) {
1337-
users(limit: $limit) {
1338-
id
1339-
name
1413+
const allPostsQuery = gql`
1414+
query {
1415+
posts {
1416+
id
1417+
name
1418+
}
13401419
}
1341-
}`
1342-
1343-
function MyComponent() {
1344-
const { loading, error, data } = useQuery<
1345-
User,
1346-
{ limit: number },
1347-
CustomError
1348-
>(HOMEPAGE_QUERY, {
1349-
variables: {
1350-
limit: 10
1351-
}
1352-
})
1420+
`
13531421

1354-
if (loading) return 'Loading...'
1355-
if (error) return 'Something Bad Happened'
1422+
function Posts() {
1423+
const { loading, error, data, refetch } = useQuery(allPostsQuery)
13561424

13571425
return (
1358-
<ul>
1359-
{data.users.map(({ id, name }) => (
1360-
<li key={id}>{name}</li>
1361-
))}
1362-
</ul>
1426+
<>
1427+
<h2>Add post</h2>
1428+
<AddPost />
1429+
<h2>Posts</h2>
1430+
<button onClick={() => refetch()}>Reload</button>
1431+
<PostList loading={loading} error={error} data={data} />
1432+
</>
13631433
)
13641434
}
1435+
1436+
...
13651437
```
13661438
13671439
## Community

examples/typescript/codegen.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { CodegenConfig } from '@graphql-codegen/cli'
2+
3+
const config: CodegenConfig = {
4+
schema: 'https://create-react-app-server-kqtv5azt3q-ew.a.run.app',
5+
documents: ['src/**/*.tsx'],
6+
ignoreNoDocuments: true, // for better experience with the watcher
7+
generates: {
8+
'./src/gql/': {
9+
preset: 'client'
10+
}
11+
}
12+
}
13+
14+
export default config

examples/typescript/package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
"graphql-hooks-memcache": "^3.2.0",
1212
"react": "^18.0.0",
1313
"react-dom": "^18.0.0",
14-
"react-scripts": "^5.0.0",
15-
"typescript": "^4.1.2"
14+
"react-scripts": "^5.0.0"
1615
},
1716
"scripts": {
1817
"start": "react-scripts start",
1918
"build": "react-scripts build",
2019
"test": "react-scripts test",
21-
"eject": "react-scripts eject"
20+
"eject": "react-scripts eject",
21+
"generate-schema": "graphql-codegen",
22+
"prestart": "npm run generate-schema"
2223
},
2324
"browserslist": [
2425
">0.2%",
@@ -31,5 +32,12 @@
3132
"react-app",
3233
"react-app/jest"
3334
]
35+
},
36+
"devDependencies": {
37+
"@graphql-codegen/cli": "^5.0.0",
38+
"@graphql-codegen/client-preset": "^4.1.0",
39+
"graphql": "^16.8.1",
40+
"graphql-codegen-typescript-client": "0.18.2",
41+
"typescript": "^5.3.3"
3442
}
3543
}

examples/typescript/src/App.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
useMutation,
77
useQuery
88
} from 'graphql-hooks'
9-
import React, { useState } from 'react'
9+
import { useState } from 'react'
10+
import { graphql } from './gql'
11+
import { GetAllPostsQuery } from './gql/graphql'
1012

1113
interface PostData {
1214
id: string
@@ -18,33 +20,33 @@ const client = new GraphQLClient({
1820
url: 'https://create-react-app-server-kqtv5azt3q-ew.a.run.app'
1921
})
2022

21-
export const allPostsQuery = `
22-
query {
23+
export const allPostsQuery = graphql(`
24+
query GetAllPosts {
2325
allPosts {
2426
id
2527
title
2628
url
2729
}
2830
}
29-
`
31+
`)
3032

31-
const createPostMutation = `
33+
const createPostMutation = graphql(`
3234
mutation CreatePost($title: String!, $url: String!) {
3335
createPost(title: $title, url: $url) {
3436
id
3537
}
3638
}
37-
`
39+
`)
3840

39-
const postQuery = `
41+
const postQuery = graphql(`
4042
query Post($id: ID!) {
4143
Post(id: $id) {
4244
id
4345
url
4446
title
4547
}
4648
}
47-
`
49+
`)
4850

4951
function AddPost() {
5052
const [title, setTitle] = useState('')
@@ -104,20 +106,23 @@ function PostList({
104106
}: {
105107
loading: boolean
106108
error?: APIError
107-
data: any
109+
data?: GetAllPostsQuery
108110
}) {
109111
if (loading) return <p>Loading...</p>
110112
if (error) return <p>Error!</p>
111113
if (!data || !data.allPosts || !data.allPosts.length) return <p>No posts</p>
112114

113115
return (
114116
<ul>
115-
{data.allPosts.map((post: PostData) => (
116-
<li key={post.id}>
117-
<a href={post.url}>{post.title}</a>
118-
<small>(id: {post.id})</small>
119-
</li>
120-
))}
117+
{data.allPosts.map((post: PostData | null) => {
118+
if (!post) return undefined
119+
return (
120+
<li key={post.id}>
121+
<a href={post.url}>{post.title}</a>
122+
<small>(id: {post.id})</small>
123+
</li>
124+
)
125+
})}
121126
</ul>
122127
)
123128
}

0 commit comments

Comments
 (0)