Home

Awesome

graphql-hooks

ci Coverage Status bundlephobia npm lerna

🎣 Minimal hooks-first GraphQL client.

Features

Install

npm install graphql-hooks

or

yarn add graphql-hooks

Support

Consider polyfilling:

Quick Start

First you'll need to create a client and wrap your app with the provider:

import { GraphQLClient, ClientContext } from 'graphql-hooks'

const client = new GraphQLClient({
  url: '/graphql'
})

function App() {
  return (
    <ClientContext.Provider value={client}>
      {/* children */}
    </ClientContext.Provider>
  )
}

Now in your child components you can make use of useQuery

import { useQuery } from 'graphql-hooks'

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

function MyComponent() {
  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>
  )
}

Why graphql-hooks?

The first thing you may ask when seeing graphql-hooks is "Why not use Apollo hooks?". It's the comparison most will make. In fact, there's an article comparing the two over on LogRocket.

We believe graphql-hooks is a great choice as a hooks-first GraphQL client due to its concise API and package size.

In terms of performance, this is more of a grey area as we have no official benchmarks yet.

If you need a client that offers more customization such as advanced cache configuration, then apollo-hooks may work out to be a good choice for your project if bundle size is not an issue.

ProsCons
Small in sizeLess "advanced" caching configuration
Concise API
Quick to get up and running

Table of Contents

API

GraphQLClient

Usage:

import { GraphQLClient } from 'graphql-hooks'
const client = new GraphQLClient(config)

config: Object containing configuration properties

client methods

ClientContext

ClientContext is the result of React.createContext() - meaning it can be used directly with React's new context API:

Example:

import { ClientContext } from 'graphql-hooks'

function App() {
  return (
    <ClientContext.Provider value={client}>
      {/* children can now consume the client context */}
    </ClientContext.Provider>
  )
}

To access the GraphQLClient instance, call React.useContext(ClientContext):

import React, { useContext } from 'react'
import { ClientContext } from 'graphql-hooks'

function MyComponent() {
  const client = useContext(ClientContext)
}

useQuery

Usage:

const state = useQuery(query, [options])

Example:

import { useQuery } from 'graphql-hooks'

function MyComponent() {
  const { loading, error, data } = useQuery(query)

  if (loading) return 'Loading...'
  if (error) return 'Something bad happened'

  return <div>{data.thing}</div>
}

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.

useQuery return value

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.

Usage:

const [queryFn, state] = useManualQuery(query, [options])

Example:

import { useManualQuery } from 'graphql-hooks'

function MyComponent(props) {
  const [fetchUser, { loading, error, data }] = useManualQuery(GET_USER_QUERY, {
    variables: { id: props.userId }
  })

  return (
    <div>
      <button onClick={fetchUser}>Get User!</button>
      {error && <div>Failed to fetch user<div>}
      {loading && <div>Loading...</div>}
      {data && <div>Hello ${data.user.name}</div>}
    </div>
  )
}

If you don't know certain options when declaring the useManualQuery you can also pass the same options to the query function itself when calling it:

import { useManualQuery } from 'graphql-hooks'

function MyComponent(props) {
  const [fetchUser] = useManualQuery(GET_USER_QUERY)

  const fetchUserThenSomething = async () => {
    const user = await fetchUser({
      variables: { id: props.userId }
    })
    return somethingElse()
  }

  return (
    <div>
      <button onClick={fetchUserThenSomething}>Get User!</button>
    </div>
  )
}

useQueryClient

Will return the graphql client provided to ClientContext.Provider as value

Usage:

const client = useQueryClient()

Example:

import { useQueryClient } from 'graphql-hooks'

function MyComponent() {
  const client = useQueryClient()

  return <div>...</div>
}

useMutation

Mutations unlike Queries are not cached.

Usage:

const [mutationFn, state, resetFn] = useMutation(mutation, [options])

Example:

import { useMutation } from 'graphql-hooks'

const UPDATE_USER_MUTATION = `mutation UpdateUser(id: String!, name: String!) {
  updateUser(id: $id, name: $name) {
    name
  }
}`

function MyComponent({ id, name }) {
  const [updateUser] = useMutation(UPDATE_USER_MUTATION)
  const [newName, setNewName] = useState(name)

  return (
    <div>
      <input
        type="text"
        value={newName}
        onChange={e => setNewName(e.target.value)}
      />
      <button
        onClick={() => updateUser({ variables: { id, name: newName } })}
      />
    </div>
  )
}

The options object that can be passed either to useMutation(mutation, options) or mutationFn(options) can be set with the following properties:

In addition, there is an option to reset the current state before calling the mutation again, by calling resetFn(desiredState) where desiredState is optional and if passed, it will override the initial state with:

useSubscription

To use subscription you can use either subscriptions-transport-ws or graphql-ws

API

useSubscription(operation, callback)

Usage:

First, follow the quick start guide to create the client and provider. Then we need to update the config for our GraphQLClient passing in the subscriptionClient:

import { GraphQLClient } from 'graphql-hooks'
import { SubscriptionClient } from 'subscriptions-transport-ws'
// or
import { createClient } from 'graphql-ws'

const client = new GraphQLClient({
  url: 'http://localhost:8000/graphql',
  subscriptionClient: () =>
    new SubscriptionClient('ws://localhost:8000/graphql', {
      /* additional config options */
    }),
  // or
  subscriptionClient: () =>
    createClient({
      url: 'ws://localhost:8000/graphql'
      /* additional config options */
    })
})

Next, within our React app, we can now make use of the useSubscription hook.

import React, { useState } from 'react'
import { useSubscription } from 'graphql-hooks'

const TOTAL_COUNT_SUBSCRIPTION = `
  subscription TotalCount {
    totalCount {
      count
    }
  }
`

function TotalCountComponent() {
  const [count, setCount] = useState(0)
  const [error, setError] = useState(null)

  useSubscription({ query: TOTAL_COUNT_SUBSCRIPTION }, ({ data, errors }) => {
    if (errors && errors.length > 0) {
      // handle your errors
      setError(errors[0])
      return
    }

    // all good, handle the gql result
    setCount(data.totalCount.count)
  })

  if (error) {
    return <span>An error occurred {error.message}</span>
  }

  return <div>Current count: {count}</div>
}

Working Example:

See our subscription example which has both the client and server code to integrate subscriptions into your application.

See also the full WS transport example if you want to see how to send every operation through WebSocket.

Guides

SSR

See graphql-hooks-ssr for an in depth guide.

Pagination

GraphQL Pagination can be implemented in various ways and it's down to the consumer to decide how to deal with the resulting data from paginated queries. Take the following query as an example of offset pagination:

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

In this query, the $first variable is used to limit the number of posts that are returned and the $skip variable is used to determine the offset at which to start. We can use these variables to break up large payloads into smaller chunks, or "pages". We could then choose to display these chunks as distinct pages to the user, or use an infinite loading approach and append each new chunk to the existing list of posts.

Separate pages

Here is an example where we display the paginated queries on separate pages:

import { React, useState } from 'react'
import { useQuery } from 'graphql-hooks'

export default function PostList() {
  // set a default offset of 0 to load the first page
  const [skipCount, setSkipCount] = useState(0)

  const { loading, error, data } = useQuery(allPostsQuery, {
    variables: { skip: skipCount, first: 10 }
  })

  if (error) return <div>There was an error!</div>
  if (loading && !data) return <div>Loading</div>

  const { allPosts, _allPostsMeta } = data
  const areMorePosts = allPosts.length < _allPostsMeta.count

  return (
    <section>
      <ul>
        {allPosts.map(post => (
          <li key={post.id}>
            <a href={post.url}>{post.title}</a>
          </li>
        ))}
      </ul>
      <button
        // reduce the offset by 10 to fetch the previous page
        onClick={() => setSkipCount(skipCount - 10)}
        disabled={skipCount === 0}
      >
        Previous page
      </button>
      <button
        // increase the offset by 10 to fetch the next page
        onClick={() => setSkipCount(skipCount + 10)}
        disabled={!areMorePosts}
      >
        Next page
      </button>
    </section>
  )
}

Infinite loading

Here is an example where we append each paginated query to the bottom of the current list:

import { React, useState } from 'react'
import { useQuery } from 'graphql-hooks'

// use options.updateData to append the new page of posts to our current list of posts
const updateData = (prevData, data) => ({
  ...data,
  allPosts: [...prevData.allPosts, ...data.allPosts]
})

export default function PostList() {
  const [skipCount, setSkipCount] = useState(0)

  const { loading, error, data } = useQuery(allPostsQuery, {
    variables: { skip: skipCount, first: 10 },
    updateData
  })

  if (error) return <div>There was an error!</div>
  if (loading && !data) return <div>Loading</div>

  const { allPosts, _allPostsMeta } = data
  const areMorePosts = allPosts.length < _allPostsMeta.count

  return (
    <section>
      <ul>
        {allPosts.map(post => (
          <li key={post.id}>
            <a href={post.url}>{post.title}</a>
          </li>
        ))}
      </ul>
      {areMorePosts && (
        <button
          // set the offset to the current number of posts to fetch the next page
          onClick={() => setSkipCount(allPosts.length)}
        >
          Show more
        </button>
      )}
    </section>
  )
}

Refetch queries with mutations subscription

We can have a query to automatically refetch when any mutation from a provided list is executed. In the following example we are refetching a list of posts for a given user.

Example

export const allPostsByUserIdQuery = `
  query allPosts($userId: Int!) {
    allPosts(userId: $userId) {
      id
      title
      url
    }
  }
`

export const createPostMutation = `
  mutation createPost($userId: Int!, $text: String!) {
    createPost(userId: $userId, text: $text) {
      id
      title
      url
    }
  }
`

const myUserId = 5

useQuery(allPostsByUserIdQuery, {
  variables: {
    userId: myUserId
  },
  refetchAfterMutations: [
    {
      mutation: createPostMutation,
      filter: variables => variables.userId === myUserId
    }
  ]
})

Manually updating the cache after some mutation

There are two ways to reach that:

By re-fetching the query

import { useMutation, useQueryClient } from 'graphql-hooks'
import React from 'react'

const MY_MUTATION = `...`
const MY_QUERY = `...`

export default function MyComponent() {
  const client = useQueryClient()
  const [applyMutation, { ... }] = useMutation(MY_MUTATION, {
    onSuccess: () => client.invalidateQuery(MY_QUERY)
  })

  return (
    ...
  )
}

By overriding the old state in the cache without re-fetching data

import { useMutation, useQueryClient } from 'graphql-hooks'
import React from 'react'

const MY_MUTATION = `...`
const MY_QUERY = `...`

export default function MyComponent() {
  const client = useQueryClient()
  const [applyMutation, { ... }] = useMutation(MY_MUTATION, {
    onSuccess: (result) => {
      client.setQueryData(MY_QUERY, oldState => [
        ...oldState,
        result,
      ])
    }
  })

  return (
    ...
  )
}

File uploads

graphql-hooks complies with the GraphQL multipart request spec, allowing files to be used as query or mutation arguments. The same spec is also supported by popular GraphQL servers, including Apollo Server (see list of supported servers here).

If there are files to upload, the request's body will be a FormData instance conforming to the GraphQL multipart request spec.

import React, { useRef } from 'react'
import { useMutation } from 'graphql-hooks'

const uploadPostPictureMutation = `
  mutation UploadPostPicture($picture: Upload!) {
    uploadPostPicture(picture: $picture) {
      id
      pictureUrl
    }
  }
`

export default function PostForm() {
  // File input is always uncontrolled in React.
  // See: https://reactjs.org/docs/uncontrolled-components.html#the-file-input-tag.
  const fileInputRef = useRef(null)

  const [uploadPostPicture] = useMutation(uploadPostPictureMutation)

  const handleSubmit = event => {
    event.preventDefault()

    uploadPostPicture({
      variables: {
        picture: fileInputRef.current.files[0]
      }
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input accept="image/*" ref={fileInputRef} type="file" />
      <button>Upload</button>
    </form>
  )
}

File uploads Node.js

import { FormData } from 'formdata-node'
import { fileFromPath } from 'formdata-node/file-from-path'

const client = new GraphQLClient({
  url: 'https://domain.com/graphql',
  fetch: require('node-fetch'),
  FormData
})

const uploadPostPictureMutation = `
  mutation UploadPostPicture($picture: Upload!) {
    uploadPostPicture(picture: $picture) {
      id
      pictureUrl
    }
  }
`

const { data, error } = await client.request({
  query: uploadPostPictureMutation,
  variables: { picture: await fileFromPath('some-file.txt') }
})

HTTP Get support

Using GET for queries can be useful, especially when implementing any sort of HTTP caching strategy. There are two ways you can do this:

Per Query

const { loading, error, data } = useQuery(MY_QUERY, {
  fetchOptionsOverrides: { method: 'GET' }
})

// same goes for useManualQuery
const [fetchSomething] = useManualQuery(MY_QUERY, {
  fetchOptionsOverrides: { method: 'GET' }
})

For All Queries

When you create your client, set the useGETForQueries option as true:

const client = new GraphQLClient({
  url: '/graphql',
  useGETForQueries: true
})

Authentication

You can have access to the graphql-hooks client context by using React's new context API. ClientContext is actually the result of React.createContext().

Login Example

import React, { useState, useContext } from 'react'
import { useMutation, ClientContext } from 'graphql-hooks'

const LOGIN_MUTATION = `mutation LoginUser (name: String!, password: String!) {
  loginUser(name: $name, password: $password) {
    token
  }
}`

const Login = () => {
  const client = useContext(ClientContext)
  const [loginUserMutation] = useMutation(LOGIN_MUTATION)
  const [userName, setUserName] = useState()
  const [password, setPassword] = useState()

  const handleLogin = async e => {
    e.preventDefault()
    const { data, error } = await loginUserMutation({
      variables: { userName, password }
    })
    if (error) {
      // your code to handle login error
    } else {
      const { token } = data.loginUser
      client.setHeader('Authorization', `Bearer ${token}`)
      // your code to handle token in browser and login redirection
    }
  }
  return (
    <form onSubmit={handleLogin}>
      User Name:{' '}
      <input
        type={'text'}
        value={userName}
        onChange={e => setUserName(e.target.value)}
      />
      PassWord: <input
        type={'password'}
        value={password}
        onChange={e => setPassword(e.target.value)}
      />
      <input type={'submit'} value={'Login'} />
    </form>
  )
}

export default Login

In the above example we use useContext() hook to get access to the graphql-hooks clientContext. Then we request the token from the server by performing the loginUser mutation. In the case the login is successful we set the token to the client's header (client.setHeader), otherwise we need to handle the error. For more information about graphql-hooks clientContext refer to GraphQLClient section.

Fragments

Coming soon!

Migrating from Apollo

For a real life example, compare the next.js with-apollo vs with-graphql-hooks. We have feature parity and the main-*.js bundle is a whopping 93% smaller (7.9KB vs 116KB).

ApolloClient ➡️ GraphQLClient

- import { ApolloClient } from 'apollo-client'
- import { InMemoryCache } from 'apollo-cache-inmemory'
+ import { GraphQLClient } from 'graphql-hooks'
+ import memCache from 'graphql-hooks-memcache'

- const client = new ApolloClient({
-  uri: '/graphql',
-  cache: new InMemoryCache()
- })
+ const client = new GraphQLClient({
+   url: '/graphql',
+   cache: memCache()
+ })

A lot of the options you'd pass to ApolloClient are the same as GraphQLClient:

ApolloProvider ➡️ ClientContext.Provider

- import { ApolloProvider } from 'react-apollo'
+ import { ClientContext } from 'graphql-hooks'

function App({ client }) {
  return (
-    <ApolloProvider client={client}>
+    <ClientContext.Provider value={client}>
       {/* children */}
+    </ClientContext.Provider>
-    </ApolloProvider>
  )
}

Query Component ➡️ useQuery

- import { Query } from 'react-apollo'
- import gql from 'graphql-tag'
+ import { useQuery } from 'graphql-hooks'

function MyComponent() {
+ const { loading, error, data } = useQuery('...')

-  return (
-    <Query query={gql`...`}>
-     {({ loading, error, data}) => {
        if (loading) return 'Loading...'
        if (error) return 'Error :('

        return <div>{data}</div>
-      }}
-    </Query>
-  )
}

Query Component Props

A lot of options can be carried over as-is, or have direct replacements:

Not yet supported

Query Component Render Props

- <Query query={gql`...`}>
-  {(props) => {}}
- </Query>
+ const state = useQuery(`...`)

Not yet supported

Mutation Component ➡️ useMutation

- import { Mutation } from 'react-apollo'
- import gql from 'graphql-tag'
+ import { useMutation } from 'graphql-hooks'

function MyComponent() {
+ const [mutateFn, { loading, error, data }] = useMutation('...')

-  return (
-    <Mutation mutation={gql`...`}>
-     {(mutateFn, { loading, error }) => {
        if (error) return 'Error :('

        return <button disabled={loading} onClick={() => mutateFn()}>Submit</button>
-      }}
-    </Mutation>
-  )
}

Mutation Props

Not yet supported

Mutation Component Render Props

- <Mutation mutation={gql`...`}>
-  {(mutateFn, props) => {}}
- </Mutation>
+ const [mutateFn, state] = useMutation(`...`)

Not yet supported

Testing and mocking

There is a LocalGraphQLClient class you can use to mock requests without a server for testing or development purposes.

This client inherits from GraphQLClient and provides the same API, but doesn't connect to any server and instead responds to pre-defined queries.

It needs to be supplied on creation with a localQueries object, which is an object where:

// src/components/Post.js
export const allPostsQuery = `
  query {
    allPosts {
      id
      title
      url
    }
  }
`
// test/Post.test.tsx
import { allPostsQuery, createPostMutation } from '../src/components/Post'

const localQueries = {
  [allPostsQuery]: () => ({
    allPosts: [
      {
        id: 1,
        title: 'Test',
        url: 'https://example.com'
      }
    ]
  }),
  [createPostMutation]: () => ({ createPost: { id: 1 } })
}
const client = new LocalGraphQLClient({ localQueries })
const { data, error } = await client.request({
  query: allPostsQuery
})

The LocalGraphQLClient will return data and error properties in the same format as the GraphQLClient

Variables

Variables can be used in the local mock queries given to the LocalGraphQLClient, which can then be supplied to the request function:

const localQueries = {
  AddNumbersQuery: ({ a, b }) => ({
    addedNumber: a + b
  })
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
  query: 'AddNumbersQuery',
  variables: {
    a: 2,
    b: 3
  }
})
console.log(result.data.addedNumber) // Will be 5

Error mocking

Errors can be simply mocked in LocalGraphQLClient queries by using the LocalGraphQLError class:

// test/Post.test.tsx
import { allPostsQuery } from '../src/components/Post'

const localQueries = {
  [allPostsQuery]: () =>
    new LocalGraphQLError({
      httpError: {
        status: 404,
        statusText: 'Not found',
        body: 'Not found'
      }
    })
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
  query: allPostsQuery
})
console.log(result.error) // The `error` object will have an `httpError`

It is also possible to mock a partial error response (for example where one resolver encounters an error but other resolvers return successfully). To do this, include Error objects in the mock query resolver:

import { allPostsQuery } from '../src/components/Post'

const localQueries = {
  [allPostsQuery]: () => ({
    field1: 'foo',
    field2: new Error('something went wrong'),
    nested: {
      field3: new Error('a nested error')
    }
  })
}
const client = new LocalGraphQLClient({ localQueries })
const result = await client.request({
  query: allPostsQuery
})
console.log(result.data) // The `data` object will have the correct value for `field1` and `null` for any fields returning `Error` objects
console.log(result.error) // The `error` object will have a `graphQLErrors` array containing each of the `Error` objects created above

Testing with React

Example tests that use the LocalGraphQLClient are provided in the examples/create-react-app/test folder.

The test-utils.tsx is a good example of how to create a custom render function using @testing-library/react which can wrap the render of a React component in a ClientContext setup to use the LocalGraphQLClient with supplied local queries:

const customRender = (ui, options) => {
  const client = new LocalGraphQLClient({
    localQueries: options.localQueries
  })

  const Wrapper = ({ children }) => {
    return (
      <ClientContext.Provider value={client}>{children}</ClientContext.Provider>
    )
  }

  Wrapper.propTypes = {
    children: T.node.isRequired
  }

  return render(ui, {
    wrapper: Wrapper,
    ...options
  })
}

export * from '@testing-library/react'

export { customRender as render }

Using this allows us to easily render a component using the LocalGraphQLClient with local queries when writing tests:

// Comes from the above code
import { render, screen } from './test-utils'

const localQueries = {
  [allPostsQuery]: () => ({
    allPosts: [
      {
        id: 1,
        title: 'Test',
        url: 'https://example.com'
      }
    ]
  })
}

describe('Posts', () => {
  it('should render successfully', async () => {
    render(<Posts />, {
      localQueries
    })

    expect(
      await screen.findByRole('link', {
        name: /Test/i
      })
    ).toBeTruthy()
  })
})

Changing mock queries during tests

Because the LocalGraphQLClient just uses the localQueries object supplied to it, it is possible to modify or spy the local queries during tests. For example:

it('shows "No posts" if 0 posts are returned', async () => {
  jest.spyOn(localQueries, allPostsQuery).mockImplementation(() => ({
    allPosts: []
  }))

  render(<Posts />, {
    localQueries
  })

  expect(await screen.findByText('No posts')).toBeTruthy()
})

Typescript Support

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

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 DocumentNodes for your GQL queries and receive full type support.

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. Full examples of this implementation are in the examples folder.

Other

Request interceptors

It is possible to provide a custom library to handle network requests. Having that there is more control on how to handle the requests. The following example shows how to supply axios HTTP client with interceptors. It can be handy in the situations where JWT token has expired, needs to be refreshed and request retried.

import axios from 'axios'
import { buildAxiosFetch } from '@lifeomic/axios-fetch'
import { GraphQLClient } from 'graphql-hooks'

const gqlAxios = axios.create()
gqlAxios.interceptors.response.use(
  function (response) {
    return response
  },
  function (error) {
    // Handle expired JWT and refresh token
  }
)

const client = new GraphQLClient({
  url: '/graphql',
  fetch: buildAxiosFetch(gqlAxios)
})

AbortController

if you wish to abort a fetch it is possible to pass an AbortController signal to the fetchOptionsOverrides option of the fetch function. This is not graphql-hooks specific functionality, rather just an example of how to use it with the library.

import { useManualQuery } from 'graphql-hooks'

function AbortControllerExample() {
  const abortControllerRef = useRef()
  const [fetchData, { loading }] = useManualQuery(`...`)

  const handleFetch = () => {
    abortControllerRef.current = new AbortController()
    const { signal } = abortControllerRef.current
    fetchData({
      fetchOptionsOverrides: {
        signal
      }
    })
  }

  const handleAbort = () => {
    abortControllerRef.current?.abort()
  }

  return (
    <>
      <button onClick={handleFetch}>Fetch Data</button>
      {loading && <button onClick={handleAbort}>Abort</button>}
    </>
  )
}

GraphQL Document Support

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

import gql from 'graphql-tag'

const allPostsQuery = gql`
  query {
     posts {
      id
      name
     }
  }
`

function Posts() {
  const { loading, error, data, refetch } = useQuery(allPostsQuery)

  return (
    <>
      <h2>Add post</h2>
      <AddPost />
      <h2>Posts</h2>
      <button onClick={() => refetch()}>Reload</button>
      <PostList loading={loading} error={error} data={data} />
    </>
  )
}

...

Community

We now use GitHub Discussions for our community. To join, click on "Discussions". We encourage you to start a new discussion, share some ideas or ask questions from the community. If you want to see the old community posts (on Spectrum) you can access them here.

Contributors

Thanks goes to these wonderful people (emoji key):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> <tbody> <tr> <td align="center" valign="top" width="14.28%"><a href="https://twitter.com/bmullan91"><img src="https://avatars1.githubusercontent.com/u/1939483?v=4?s=100" width="100px;" alt="Brian Mullan"/><br /><sub><b>Brian Mullan</b></sub></a><br /><a href="#question-bmullan91" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Abmullan91" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=bmullan91" title="Code">💻</a> <a href="#content-bmullan91" title="Content">🖋</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=bmullan91" title="Documentation">📖</a> <a href="#example-bmullan91" title="Examples">💡</a> <a href="#ideas-bmullan91" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-bmullan91" title="Maintenance">🚧</a> <a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Abmullan91" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=bmullan91" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://jackdc.com"><img src="https://avatars0.githubusercontent.com/u/1485654?v=4?s=100" width="100px;" alt="Jack Clark"/><br /><sub><b>Jack Clark</b></sub></a><br /><a href="#question-jackdclark" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Ajackdclark" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackdclark" title="Code">💻</a> <a href="#content-jackdclark" title="Content">🖋</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackdclark" title="Documentation">📖</a> <a href="#example-jackdclark" title="Examples">💡</a> <a href="#ideas-jackdclark" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-jackdclark" title="Maintenance">🚧</a> <a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Ajackdclark" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackdclark" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="http://twitter.com/joezo"><img src="https://avatars1.githubusercontent.com/u/2870255?v=4?s=100" width="100px;" alt="Joe Warren"/><br /><sub><b>Joe Warren</b></sub></a><br /><a href="#question-Joezo" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3AJoezo" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=Joezo" title="Code">💻</a> <a href="#content-Joezo" title="Content">🖋</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=Joezo" title="Documentation">📖</a> <a href="#example-Joezo" title="Examples">💡</a> <a href="#ideas-Joezo" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-Joezo" title="Maintenance">🚧</a> <a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3AJoezo" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=Joezo" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="http://simoneb.github.io"><img src="https://avatars1.githubusercontent.com/u/20181?v=4?s=100" width="100px;" alt="Simone Busoli"/><br /><sub><b>Simone Busoli</b></sub></a><br /><a href="#question-simoneb" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Asimoneb" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=simoneb" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://jheytompkins.com"><img src="https://avatars1.githubusercontent.com/u/842246?v=4?s=100" width="100px;" alt="jhey tompkins"/><br /><sub><b>jhey tompkins</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=jh3y" title="Tests">⚠️</a> <a href="#question-jh3y" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Ajh3y" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jh3y" title="Code">💻</a> <a href="#content-jh3y" title="Content">🖋</a> <a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Ajh3y" title="Reviewed Pull Requests">👀</a></td> <td align="center" valign="top" width="14.28%"><a href="https://haroen.me"><img src="https://avatars3.githubusercontent.com/u/6270048?v=4?s=100" width="100px;" alt="Haroen Viaene"/><br /><sub><b>Haroen Viaene</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3AHaroenv" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/aribouius"><img src="https://avatars2.githubusercontent.com/u/10748727?v=4?s=100" width="100px;" alt="Ari Bouius"/><br /><sub><b>Ari Bouius</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=aribouius" title="Documentation">📖</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Aaribouius" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=aribouius" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=aribouius" title="Tests">⚠️</a></td> </tr> <tr> <td align="center" valign="top" width="14.28%"><a href="https://github.com/kkogovsek"><img src="https://avatars1.githubusercontent.com/u/8089644?v=4?s=100" width="100px;" alt="Klemen Kogovšek"/><br /><sub><b>Klemen Kogovšek</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Akkogovsek" title="Bug reports">🐛</a> <a href="#ideas-kkogovsek" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=kkogovsek" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=kkogovsek" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/wescoder"><img src="https://avatars0.githubusercontent.com/u/22945955?v=4?s=100" width="100px;" alt="Wésley Queiroz"/><br /><sub><b>Wésley Queiroz</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Awescoder" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=wescoder" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://www.good-idea.studio"><img src="https://avatars3.githubusercontent.com/u/11514928?v=4?s=100" width="100px;" alt="Joseph Thomas"/><br /><sub><b>Joseph Thomas</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Agood-idea" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=good-idea" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=good-idea" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="http://edvinasbartkus.lt"><img src="https://avatars0.githubusercontent.com/u/202988?v=4?s=100" width="100px;" alt="Edvinas Bartkus"/><br /><sub><b>Edvinas Bartkus</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=edvinasbartkus" title="Code">💻</a> <a href="#question-edvinasbartkus" title="Answering Questions">💬</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Aedvinasbartkus" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=edvinasbartkus" title="Documentation">📖</a> <a href="#example-edvinasbartkus" title="Examples">💡</a> <a href="#ideas-edvinasbartkus" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-edvinasbartkus" title="Maintenance">🚧</a> <a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Aedvinasbartkus" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=edvinasbartkus" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/olistic"><img src="https://avatars1.githubusercontent.com/u/5600126?v=4?s=100" width="100px;" alt="Matías Olivera"/><br /><sub><b>Matías Olivera</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Aolistic" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=olistic" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=olistic" title="Tests">⚠️</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=olistic" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/tcudok-jg"><img src="https://avatars3.githubusercontent.com/u/50208575?v=4?s=100" width="100px;" alt="tcudok-jg"/><br /><sub><b>tcudok-jg</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=tcudok-jg" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/heymartinadams"><img src="https://avatars2.githubusercontent.com/u/11673745?v=4?s=100" width="100px;" alt="Martin Adams"/><br /><sub><b>Martin Adams</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=heymartinadams" title="Documentation">📖</a></td> </tr> <tr> <td align="center" valign="top" width="14.28%"><a href="http://gal.js.org"><img src="https://avatars3.githubusercontent.com/u/191608?v=4?s=100" width="100px;" alt="Gal Dubitski"/><br /><sub><b>Gal Dubitski</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=galmail" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Agalmail" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=galmail" title="Documentation">📖</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=galmail" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://twitter.com/osdevisnot"><img src="https://avatars1.githubusercontent.com/u/802242?v=4?s=100" width="100px;" alt="Abhishek Shende"/><br /><sub><b>Abhishek Shende</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=osdevisnot" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Aosdevisnot" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="http://fabien.cool"><img src="https://avatars1.githubusercontent.com/u/1702255?v=4?s=100" width="100px;" alt="fabienheureux"/><br /><sub><b>fabienheureux</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Afabienheureux" title="Reviewed Pull Requests">👀</a></td> <td align="center" valign="top" width="14.28%"><a href="https://hughboylan.com"><img src="https://avatars2.githubusercontent.com/u/2158740?v=4?s=100" width="100px;" alt="Hugh Boylan"/><br /><sub><b>Hugh Boylan</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/pulls?q=is%3Apr+reviewed-by%3Ahboylan" title="Reviewed Pull Requests">👀</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/bmamouri"><img src="https://avatars2.githubusercontent.com/u/6419173?v=4?s=100" width="100px;" alt="Baqer Mamouri"/><br /><sub><b>Baqer Mamouri</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=bmamouri" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="http://www.netnotion.com"><img src="https://avatars0.githubusercontent.com/u/17315?v=4?s=100" width="100px;" alt="Guillermo Gonzalez "/><br /><sub><b>Guillermo Gonzalez </b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=helloguille" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/brookback"><img src="https://avatars0.githubusercontent.com/u/30257156?v=4?s=100" width="100px;" alt="Johan Brook"/><br /><sub><b>Johan Brook</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=brookback" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Abrookback" title="Bug reports">🐛</a> <a href="#maintenance-brookback" title="Maintenance">🚧</a></td> </tr> <tr> <td align="center" valign="top" width="14.28%"><a href="https://github.com/frikille"><img src="https://avatars2.githubusercontent.com/u/569278?v=4?s=100" width="100px;" alt="Peter Balazs"/><br /><sub><b>Peter Balazs</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=frikille" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=frikille" title="Documentation">📖</a> <a href="#example-frikille" title="Examples">💡</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=frikille" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="http://panz3r.dev"><img src="https://avatars3.githubusercontent.com/u/1754457?v=4?s=100" width="100px;" alt="Mattia Panzeri"/><br /><sub><b>Mattia Panzeri</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=panz3r" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=panz3r" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/lynxtaa"><img src="https://avatars1.githubusercontent.com/u/22496946?v=4?s=100" width="100px;" alt="Alex Kondratyuk"/><br /><sub><b>Alex Kondratyuk</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=lynxtaa" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=lynxtaa" title="Tests">⚠️</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=lynxtaa" title="Documentation">📖</a> <a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Alynxtaa" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/cepelinc"><img src="https://avatars1.githubusercontent.com/u/25613873?v=4?s=100" width="100px;" alt="Matias Cepeda"/><br /><sub><b>Matias Cepeda</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=cepelinc" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/jackh726"><img src="https://avatars1.githubusercontent.com/u/31162821?v=4?s=100" width="100px;" alt="Jack Huey"/><br /><sub><b>Jack Huey</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/issues?q=author%3Ajackh726" title="Bug reports">🐛</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackh726" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackh726" title="Documentation">📖</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=jackh726" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/zanettin"><img src="https://avatars.githubusercontent.com/u/3241476?v=4?s=100" width="100px;" alt="Roman Zanettin"/><br /><sub><b>Roman Zanettin</b></sub></a><br /><a href="https://github.com/nearform/graphql-hooks/commits?author=zanettin" title="Code">💻</a> <a href="https://github.com/nearform/graphql-hooks/commits?author=zanettin" title="Tests">⚠️</a></td> <td align="center" valign="top" width="14.28%"><a href="http://edge33.github.io"><img src="https://avatars.githubusercontent.com/u/5662280?v=4?s=100" width="100px;" alt="Francesco Maida"/><br /><sub><b>Francesco Maida</b></sub></a><br /><a href="#maintenance-edge33" title="Maintenance">🚧</a></td> </tr> </tbody> </table> <!-- markdownlint-restore --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the all-contributors specification. Contributions of any kind are welcome!