• Stars
    star
    1,878
  • Rank 24,681 (Top 0.5 %)
  • Language
    TypeScript
  • License
    Other
  • Created almost 6 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🎣 Minimal hooks-first GraphQL client

graphql-hooks

ci Coverage Status bundlephobia npm lerna

🎣 Minimal hooks-first GraphQL client.

Features

  • 🥇 First-class hooks API
  • ⚖️ Tiny bundle: only 7.6kB (2.8 gzipped)
  • 📄 Full SSR support: see graphql-hooks-ssr
  • 🔌 Plugin Caching: see graphql-hooks-memcache
  • 🔥 No more render props hell
  • ⏳ Handle loading and error states with ease

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.

Pros Cons
Small in size Less "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

  • url: The URL of your GraphQL HTTP server. If not specified, you must enable fullWsTransport and provide a valid subscriptionClient; otherwise is required.
  • fullWsTransport: Boolean - set to true if you want to use subscriptionClient to also send query and mutations via WebSocket; defaults to false
  • ssrMode: Boolean - set to true when using on the server for server-side rendering; defaults to false
  • useGETForQueries: Boolean - set to true to use HTTP GET method for all queries; defaults to false. See HTTP Get Support for more info
  • subscriptionClient: The WebSocket client configuration. Accepts either an instance of SubscriptionClient from subscriptions-transport-ws or Client from graphql-ws. A factory function is also accepted e.g. to avoid the creation of the client in SSR environments.
  • cache (Required if ssrMode is true, otherwise optional): Object with the following methods:
    • cache.get(key)
    • cache.set(key, data)
    • cache.delete(key)
    • cache.clear()
    • cache.keys()
    • getInitialState()
    • See graphql-hooks-memcache as a reference implementation
  • fetch(url, options): Fetch implementation - defaults to the global fetch API. Check Request interceptors for more details how to manage fetch.
  • FormData: FormData implementation - defaults to the global FormData API. Polyfill this in a node.js environment. See file-uploads-nodejs for more info.
  • fetchOptions: See MDN for info on what options can be passed
  • headers: Object, e.g. { 'My-Header': 'hello' }
  • logErrors: Boolean - defaults to true
  • middleware: Accepts an array of middleware functions, default: none, see more in middlewares readme
  • onError({ operation, result }): Custom error handler
    • operation: Object with query, variables and operationName
    • result: Object containing data and error object that contains fetchError, httpError and graphqlErrors

client methods

  • client.setHeader(key, value): Updates client.headers adding the new header to the existing headers
  • client.setHeaders(headers): Replaces client.headers
  • client.removeHeader(key): Updates client.headers removing the header if it exists
  • client.logErrorResult({ operation, result }): Default error logger; useful if you'd like to use it inside your custom onError handler
  • request(operation, options): Make a request to your GraphQL server; returning a Promise
    • operation: Object with query, variables and operationName
  • options.fetchOptionsOverrides: Object containing additional fetch options to be added to the default ones passed to new GraphQLClient(config)
  • options.responseReducer: Reducer function to pick values from the original Fetch Response object. Values are merged to the request response under the data key. Example usage: {responseReducer: (data, response) => ({...data, myKey: response.headers.get('content-length)})
  • client.invalidateQuery(query): Will delete the older cache, re-fetch the new data using the same query, and store it in the cache as a new value
    • query: The GraphQL query as a plain string to be re-fetched, or an Operation object (with query, variables and operationName)
  • client.setQueryData(query, (oldState) => [...oldState, newState]]): Will override the older cache state with the new one provided by the function return
    • query: The GraphQL query as a plain string, or an Operation object (with query, variables and operationName)
    • (oldState) => [...oldState, newState]]: The callback function with returns will be the new state stored in the cache.
      • oldState: The old value stored in the cache

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.

  • query: Your GraphQL query as a plain string
  • 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.
    • persisted: Boolean - defaults to false; Pass true if your graphql server supports persisted flag to serve persisted queries.
    • useCache: Boolean - defaults to true; cache the query result
    • skip: Boolean - defaults to false; do not execute the query if set to true
    • skipCache: Boolean - defaults to false; If true it will by-pass the cache and fetch, but the result will then be cached for subsequent calls. Note the refetch function will do this automatically
    • ssr: Boolean - defaults to true. Set to false if you wish to skip this query during SSR
    • fetchOptionsOverrides: Object - Specific overrides for this query. See MDN for info on what options can be passed
    • updateData(previousData, data): Function - Custom handler for merging previous & new query results; return value will replace data in useQuery return value
      • previousData: Previous GraphQL query or updateData result
      • data: New GraphQL query result
    • client: GraphQLClient - If a GraphQLClient is explicitly passed as an option, then it will be used instead of the client from the ClientContext.
    • refetchAfterMutations: String | Object | (String | Object)[] - You can specify when a mutation should trigger query refetch.
      • If it's a string, it's the mutation string
      • If it's an object then it has properties mutation and filter
        • mutation: String - The mutation string
        • refetchOnMutationError: boolean (optional, defaults to true) - It indicates whether the query must be re-fetched if the mutation returns an error
        • filter: Function (optional) - It receives mutation's variables as parameter and blocks refetch if it returns false
      • If it's an array, the elements can be of either type above

useQuery return value

const { loading, error, data, refetch, cacheHit } = useQuery(QUERY)
  • loading: Boolean - true if the query is in flight
  • data: Object - the result of your GraphQL query
  • refetch(options): Function - useful when refetching the same query after a mutation; NOTE this presets skipCache=true & will bypass the options.updateData function that was passed into useQuery. You can pass a new updateData into refetch if necessary.
    • options: Object - options that will be merged into the options that were passed into useQuery (see above).
  • cacheHit: Boolean - true if the query result came from the cache, useful for debugging
  • error: Object - Set if at least one of the following errors has occurred and contains:
    • fetchError: Object - Set if an error occurred during the fetch call
    • httpError: Object - Set if an error response was returned from the server
    • graphQLErrors: Array - Populated if any errors occurred whilst resolving the 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:

  • variables: Object e.g. { limit: 10 }
  • operationName: If your query has multiple operations, pass the name of the operation you wish to execute.
  • fetchOptionsOverrides: Object - Specific overrides for this query. See MDN for info on what options can be passed
  • client: GraphQLClient - If a GraphQLClient is explicitly passed as an option, then it will be used instead of the client from the ClientContext.
  • onSuccess: A function to be called after the mutation has been finished with success without raising any error

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:

  • data: Object - the data
  • error: Error - the error
  • loading: Boolean - true if it is still loading
  • cacheHit: Boolean - true if the result was cached

useSubscription

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

API

useSubscription(operation, callback)

  • operation: Object - The GraphQL operation 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.
    • client: GraphQLClient - If a GraphQLClient is explicitly passed as an option, then it will be used instead of the client from the ClientContext.
  • callback: Function - This will be invoked when the subscription receives an event from your GraphQL server - it will receive an object with the typical GraphQL response of { data: <your result>, errors?: [Error] }

Usage:

First follow the quick start guide to create the client and povider. 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 execute. 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 overring 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 the to the graphql-hooks client context by using the 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 success 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:

  • uri ➡️ url
  • fetchOptions
  • onError - the function signature is slightly different
  • headers
  • fetch
  • cache

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:

  • query ➡️ useQuery(query): Remove any usage of gql and pass your queries as strings.
  • variables ➡️ useQuery(query, { variables })
  • ssr ➡️ useQuery(query, { ssr })
  • Fetch Policies: See #75 for more info
    • cache-first: This the default behaviour of graphql-hooks
    • cache-and-network: The refetch function provides this behaviour it will set loading: true, but the old data will be still set until the fetch resolves.
    • network-only ➡️ useQuery(QUERY, { skipCache: true })
    • cache-only: Not supported
    • no-cache ➡️ useQuery(QUERY, { useCache: false })

Not yet supported

  • errorPolicy: Any error will set the error to be truthy. See useQuery for more details.
  • pollInterval
  • notifyOnNetworkStatusChange
  • skip
  • onCompleted: Similar ability if using useManualQuery
  • onError: Similar ability if using useManualQuery
  • partialRefetch

Query Component Render Props

- <Query query={gql`...`}>
-  {(props) => {}}
- </Query>
+ const state = useQuery(`...`)
  • props.loading ➡️ const { loading } = useQuery('...')
  • props.error ➡️ const { error } = useQuery('...'): The error value from useQuery is Boolean the details of the error can be found in either:
    • state.fetchError
    • state.httpError
    • state.graphQLErrors
  • props.refetch ️➡️ const { refetch } = useQuery('...')
  • props.updateData(prevResult, options) ️➡️ state.updateData(prevResult, newResult)

Not yet supported

  • props.networkStatus
  • props.startPolling
  • props.stopPolling
  • props.subscribeToMore

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

  • mutation ➡️ useMutation(mutation) - no need to wrap it in gql
  • variables ➡️️ useMutation(mutation, { variables }) or mutateFn({ variables })
  • ignoreResults ➡️️️️ const [mutateFn] = useMutation(mutation)
  • onCompleted ➡️ ️mutateFn().then(onCompleted)
  • onError ➡️ mutateFn().then(({ error }) => {...})

Not yet supported

  • update: Coming soon #52
  • optimisticResponse
  • refetchQueries
  • awaitRefetchQueries
  • context

Mutation Component Render Props

- <Mutation mutation={gql`...`}>
-  {(mutateFn, props) => {}}
- </Mutation>
+ const [mutateFn, state] = useMutation(`...`)
  • props.data ➡️ const [mutateFn, { data }] = useMutation()
  • props.loading ➡️ const [mutateFn, { loading }] = useMutation()
  • props.error ➡️ const [mutateFn, { error }] = useMutation(): The the details of the error can be found in either:
    • state.fetchError
    • state.httpError
    • state.graphQLErrors
  • client ️➡️️ const client = useContext(ClientContext) see ClientContext

Not yet supported

  • called

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:

  • the keys are the queries defined in the application;
  • the values are query functions returning the mocked data.
// 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.js 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 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()
})

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>}
    </>
  )
}

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


Brian Mullan

💬 🐛 💻 🖋 📖 💡 🤔 🚧 👀 ⚠️

Jack Clark

💬 🐛 💻 🖋 📖 💡 🤔 🚧 👀 ⚠️

Joe Warren

💬 🐛 💻 🖋 📖 💡 🤔 🚧 👀 ⚠️

Simone Busoli

💬 🐛 📖

jhey tompkins

⚠️ 💬 🐛 💻 🖋 👀

Haroen Viaene

🐛

Ari Bouius

📖 🐛 💻 ⚠️

Klemen Kogovšek

🐛 🤔 💻 ⚠️

Wésley Queiroz

🐛 💻

Joseph Thomas

🐛 💻 ⚠️

Edvinas Bartkus

💻 💬 🐛 📖 💡 🤔 🚧 👀 ⚠️

Matías Olivera

🐛 💻 ⚠️ 📖

tcudok-jg

💻

Martin Adams

📖

Gal Dubitski

💻 🐛 📖 ⚠️

Abhishek Shende

💻 🐛

fabienheureux

👀

Hugh Boylan

👀

Baqer Mamouri

💻

Guillermo Gonzalez

💻

Johan Brook

💻 🐛 🚧

Peter Balazs

💻 📖 💡 ⚠️

Mattia Panzeri

💻 ⚠️

Alex Kondratyuk

💻 ⚠️ 📖 🐛

Matias Cepeda

📖

Jack Huey

🐛 💻 📖 ⚠️

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

More Repositories

1

temporal_tables

Postgresql temporal_tables extension in PL/pgSQL, without the need for external c extension.
PLpgSQL
429
star
2

fast-jwt

Fast JSON Web Token implementation
JavaScript
330
star
3

nscale

Deployment just got easy
JavaScript
325
star
4

react-animation

Animation components and styles for React projects
JavaScript
285
star
5

sql

SQL injection protection module
JavaScript
201
star
6

react-browser-hooks

React Browser Hooks
JavaScript
128
star
7

udaru

Open source Access Manager for node.js
JavaScript
125
star
8

node-cephes

Implementation of special functions and distributions mathematical functions from the cephes library.
C
115
star
9

the-fastify-workshop

A workshop about Fastify
JavaScript
113
star
10

gammaray

Node.js vulnerability scanner
Go
104
star
11

autopsy

dissect your dead node services with mdb via a smart os vm
JavaScript
89
star
12

fastify-auth0-verify

Auth0 verification plugin for Fastify
JavaScript
87
star
13

titus

Deploy useful features in sprint one
JavaScript
61
star
14

micro-services-tutorial-iot

An instructor led microservices workshop
JavaScript
54
star
15

developing-microservices

A workshop on microservices in nodejs
JavaScript
54
star
16

heap-profiler

Heap dump and sample profiler generator for Node.
JavaScript
52
star
17

docker-cloudwatch

Docker Cloudwatch
JavaScript
46
star
18

promises-workshop

Broken Promises Exercises
HTML
45
star
19

polaris

NearForm multi-platform application accelerator
JavaScript
35
star
20

nscale-workshop

Nodeconf.eu nscale workshop
35
star
21

well

well
JavaScript
35
star
22

nceubadge

The NodeConf EU 2017 Badge
HTML
34
star
23

owasp-top-ten-workshop

NearForm OWASP Top Ten Security Vulnerabilities Workshop
JavaScript
34
star
24

graphql-auto-federate

Automatically federate a Mercurius GraphQL service
JavaScript
32
star
25

trail

Audit trail log service
JavaScript
32
star
26

slow-rest-api

A REST API that is slow
HTML
31
star
27

stats

📊 Collect stats about your node.js process 📊
JavaScript
29
star
28

node-hidden-markov-model-tf

A trainable Hidden Markov Model with Gaussian emissions using TensorFlow.js
JavaScript
28
star
29

autocannon-ui

A graphical user interface for autocannon providing the same user experience
JavaScript
28
star
30

react-pwa

Hackernews Progressive Web Application built with React
JavaScript
28
star
31

open-banking-reference-app

NearForm reference application for open banking - https://community.nearform.com/api-banking
TypeScript
27
star
32

react-redux-typescript-saga-immutable

Sample React boilerplate written in TypeScript, including React Router, Redux, Redux Saga, ImmutableJS and Styled Components.
TypeScript
27
star
33

fastify-overview-ui

UI for fastify-overview
JavaScript
26
star
34

minishift-demo

Demo for local development using minishift
Shell
25
star
35

reviewbot

A bot to assist with code reviews via AI
JavaScript
25
star
36

get-jwks

Fetch utils for JWKS keys
JavaScript
23
star
37

sharing-components

JavaScript
23
star
38

tf-modules-example

Code example for the reusable, configurable Terraform modules blog post
HCL
22
star
39

aws-proxy-pattern

Terraform module to create a transparent proxy within AWS
HCL
20
star
40

openapi-transformer-toolkit

Automate design-first API workflows by generating schemas and types from OpenAPI specs.
TypeScript
20
star
41

fastify-casbin

A plugin for Fastify that adds support for Casbin
JavaScript
19
star
42

the-graphql-workshop

A workshop about GraphQL with mercurius
JavaScript
18
star
43

choo-pwa

PWA with Choo
JavaScript
17
star
44

no-gres

A small module to mock pg for testing purposes.
JavaScript
17
star
45

nscale-docs

Documentation for nscale
17
star
46

optic

App for generating OTP tokens for 2FA protected accounts
JavaScript
17
star
47

initium-platform

A set of Kubernetes add-ons with optimal configuration and test coverage to create a day zero platform for your code
Go
16
star
48

optic-release-automation-action

Automate the release process of your npm modules, apps and actions
JavaScript
15
star
49

brokeneck

Admin UI for Auth0, Azure AD and AWS Cognito
JavaScript
15
star
50

node-test-runner-workshop

The Node.js Test Runner Workshop
JavaScript
14
star
51

graphql-hooks-workshop

HTML
14
star
52

react-patterns-workshop

A workshop which covers intermediate and advanced React usage patterns
JavaScript
14
star
53

node-test-github-reporter

A GitHub test reporter for the Node.js test runner
JavaScript
14
star
54

mira

The Mira Accelerator fast-tracks the setup of common Amazon Web Services (AWS) Serverless infrastructure
TypeScript
13
star
55

react-native-apple-wallet-demo

Create Custom Apple Wallet Passes with React Native and Fastify
JavaScript
13
star
56

langchain-google-calendar

A spike project: allows natural language to create actions in Google Calendar
TypeScript
12
star
57

fastify-casbin-rest

A plugin for Fastify that adds support for Casbin's REST model
JavaScript
12
star
58

cloudwatchlogs-stream

Stream interfacet to CloudWatch Logs
JavaScript
11
star
59

mercurius-apollo-registry

A Mercurius plugin for schema reporting to Apollo Studio
JavaScript
11
star
60

leaistic

An ElasticSearch manager
JavaScript
11
star
61

stats-to-elasticsearch

Collect and send stats about your node.js process to elasticsearch. 📊🔌📈
JavaScript
10
star
62

nceubadge2018

NodeConf EU 2018 Badge code and hardware design files
JavaScript
10
star
63

openshift-kafka

Run Apache Kafka in Openshift Origin
Smarty
10
star
64

create-stats-dashboard

📈 A wrapper to create and initialise a stats dashboard on kibana using import-kibana-dashboard, for easy manipulation of stats sent via stats-to-elasticsearch 📉
JavaScript
10
star
65

fastify-mssql

MSSQL Plugin for Fastify
JavaScript
10
star
66

mercurius-explain

A Mercurius plugin that shows the execution time of each resolver in a query
JavaScript
10
star
67

the-micro-frontends-workshop

The Micro Frontends Workshop with Module Federation
JavaScript
9
star
68

commentami

A 'google docs' like commenting system
JavaScript
9
star
69

zoom-shuffle-bot

Zoom Chatbot used to retrieve a randomized list of your current meeting's participants
JavaScript
9
star
70

jsnation-node-workshop

Our workshop at JSNation 2019
JavaScript
9
star
71

optic-expo

Secure 2FA OTP via Mobile Push Notifications
TypeScript
8
star
72

choo-data

Simple data fetching plugin for Choo with server-side rendering support
JavaScript
8
star
73

nodeconfeu-gesture-models

Ongoing attempts at gesture models
C++
8
star
74

slack-knowledgebase-chatgpt-responder

ChatGPT powered slack responder to the questions that are about NearForm knowledge base
JavaScript
8
star
75

initium

Deploy your code on day zero, avoiding vendor lock-in.
7
star
76

playwright-firebase

A plugin to handle Firebase authentication in Playwright tests
TypeScript
7
star
77

docker-container

JavaScript
7
star
78

otlp-blueprint

Open Telemetry + Jaeger + 3-tier application Blueprint
HCL
7
star
79

pwa-poc

PWA for proof of concept for sharing images, qrcode scanning and geolocation from a PWA
JavaScript
7
star
80

slidev-theme-nearform

NearForm theme for sli.dev presentations
CSS
6
star
81

saluki

Utility based CSS-in-JS theming
JavaScript
6
star
82

fastify-secrets-aws

Fastify secrets plugin for AWS Secrets Manager
JavaScript
6
star
83

fastify-cloud-run

Fastify on Google Cloud Run
JavaScript
6
star
84

iot-system

A fuge config for a demo iot system
JavaScript
6
star
85

fastify-slow-down

A slow down plugin for fastify
JavaScript
6
star
86

github-action-check-linked-issues

GitHub action to check if pull requests have their corresponding issues.
JavaScript
6
star
87

github-board-slack-notifications

Send notifications to a Slack channel for any changes that are performed in a GitHub board (Projects v2 / beta)
JavaScript
6
star
88

openshift-ansible

Ansible code for openshift
Python
5
star
89

fastify-jwt-jwks

JSON Web Key Set (JWKS) verification plugin for Fastify
JavaScript
5
star
90

memleak-exercise

JavaScript
5
star
91

github-action-notify-release

GitHub Action that automatically creates an issue with an overview of the commits that are waiting to be released
JavaScript
5
star
92

fastify-ravendb

Fastify RavenDB connection plugin
JavaScript
5
star
93

fastify-secrets-azure

Fastify secrets plugin for Azure Key Vault
JavaScript
5
star
94

anger

pub-sub tester for Nes
JavaScript
5
star
95

choo-bundles

Bundle splitting with HTTP2 push support for Choo with choo-ssr
JavaScript
5
star
96

sentinel

Sentinel - a testing & monitoring application
JavaScript
5
star
97

mercurius-explain-graphiql-plugin

A Graphiql plugin to show the results from mercurius-explain
JavaScript
5
star
98

azure-workshop

Azure Workshop on Kubernetes
JavaScript
5
star
99

webinar-streams

Examples for the streams Webinar
JavaScript
4
star
100

initium-cli

CLI tool for the Initium project
Go
4
star