Skip to content

Commit

Permalink
feat: add support for GQL document input (#1151)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
luke88jones authored Jan 19, 2024
1 parent fe18ee9 commit 02261fe
Show file tree
Hide file tree
Showing 20 changed files with 753 additions and 170 deletions.
152 changes: 112 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ If you need a client that offers more customization such as advanced cache confi
- [Other]
- [Request interceptors](#request-interceptors)
- [AbortController](#abortController)
- [GraphQL document support](#graphql-document-support)

## API

Expand Down Expand Up @@ -239,7 +240,7 @@ function MyComponent() {

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.

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

## `useManualQuery`

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

**Usage**:

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

`useSubscription(operation, callback)`

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

**Usage**:

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`:
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`:

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

## Typescript Support

All client methods support the ability to provide type information for response data, query variables and error responses.

```typescript
import { useQuery } from 'graphql-hooks'

type User = {
id: string
name: string
}

type CustomError = {
message: string
extensions?: Record<string, any>
}

const HOMEPAGE_QUERY = `query HomePage($limit: Int) {
users(limit: $limit) {
id
name
}
}`

function MyComponent() {
const { loading, error, data } = useQuery<
User,
{ limit: number },
CustomError
>(HOMEPAGE_QUERY, {
variables: {
limit: 10
}
})

if (loading) return 'Loading...'
if (error) return 'Something Bad Happened'

return (
<ul>
{data.users.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
)
}
```

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

```typescript
import { useQuery } from 'graphql-hooks'
import { graphql } from './gql'

const HOMEPAGE_QUERY = graphql(`query HomePage($limit: Int) {
users(limit: $limit) {
id
name
}
}`)

function MyComponent() {
// data will be typed as User objects with id, name properties
const { loading, error, data } = useQuery(HOMEPAGE_QUERY, {
variables: {
limit: 10
}
})

if (loading) return 'Loading...'
if (error) return 'Something Bad Happened'

return (
<ul>
{data.users.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
)
}
```

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.


## Other

### Request interceptors
Expand Down Expand Up @@ -1316,52 +1402,38 @@ function AbortControllerExample() {
}
```
## Typescript Support
### GraphQL Document Support
All client methods support the ability to provide type information for response data, query variables and error responses.
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`.
```typescript
import { useQuery } from 'graphql-hooks'
type User = {
id: string
name: string
}

type CustomError = {
message: string
extensions?: Record<string, any>
}
```js
import gql from 'graphql-tag'

const HOMEPAGE_QUERY = `query HomePage($limit: Int) {
users(limit: $limit) {
id
name
const allPostsQuery = gql`
query {
posts {
id
name
}
}
}`

function MyComponent() {
const { loading, error, data } = useQuery<
User,
{ limit: number },
CustomError
>(HOMEPAGE_QUERY, {
variables: {
limit: 10
}
})
`

if (loading) return 'Loading...'
if (error) return 'Something Bad Happened'
function Posts() {
const { loading, error, data, refetch } = useQuery(allPostsQuery)

return (
<ul>
{data.users.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
<>
<h2>Add post</h2>
<AddPost />
<h2>Posts</h2>
<button onClick={() => refetch()}>Reload</button>
<PostList loading={loading} error={error} data={data} />
</>
)
}

...
```
## Community
Expand Down
14 changes: 14 additions & 0 deletions examples/typescript/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
schema: 'https://create-react-app-server-kqtv5azt3q-ew.a.run.app',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client'
}
}
}

export default config
14 changes: 11 additions & 3 deletions examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
"graphql-hooks-memcache": "^3.2.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-scripts": "^5.0.0",
"typescript": "^4.1.2"
"react-scripts": "^5.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"generate-schema": "graphql-codegen",
"prestart": "npm run generate-schema"
},
"browserslist": [
">0.2%",
Expand All @@ -31,5 +32,12 @@
"react-app",
"react-app/jest"
]
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/client-preset": "^4.1.0",
"graphql": "^16.8.1",
"graphql-codegen-typescript-client": "0.18.2",
"typescript": "^5.3.3"
}
}
35 changes: 20 additions & 15 deletions examples/typescript/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
useMutation,
useQuery
} from 'graphql-hooks'
import React, { useState } from 'react'
import { useState } from 'react'
import { graphql } from './gql'
import { GetAllPostsQuery } from './gql/graphql'

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

export const allPostsQuery = `
query {
export const allPostsQuery = graphql(`
query GetAllPosts {
allPosts {
id
title
url
}
}
`
`)

const createPostMutation = `
const createPostMutation = graphql(`
mutation CreatePost($title: String!, $url: String!) {
createPost(title: $title, url: $url) {
id
}
}
`
`)

const postQuery = `
const postQuery = graphql(`
query Post($id: ID!) {
Post(id: $id) {
id
url
title
}
}
`
`)

function AddPost() {
const [title, setTitle] = useState('')
Expand Down Expand Up @@ -104,20 +106,23 @@ function PostList({
}: {
loading: boolean
error?: APIError
data: any
data?: GetAllPostsQuery
}) {
if (loading) return <p>Loading...</p>
if (error) return <p>Error!</p>
if (!data || !data.allPosts || !data.allPosts.length) return <p>No posts</p>

return (
<ul>
{data.allPosts.map((post: PostData) => (
<li key={post.id}>
<a href={post.url}>{post.title}</a>
<small>(id: {post.id})</small>
</li>
))}
{data.allPosts.map((post: PostData | null) => {
if (!post) return undefined
return (
<li key={post.id}>
<a href={post.url}>{post.title}</a>
<small>(id: {post.id})</small>
</li>
)
})}
</ul>
)
}
Expand Down
Loading

0 comments on commit 02261fe

Please sign in to comment.