Library / SDK
nearform/graphql-hooks avatar
nearform/graphql-hooks

graphql-hooks is a small React GraphQL client that does the cache itself

🎣 Minimal hooks-first GraphQL client

1,889 stars90 forksTypeScriptNOASSERTION

At a glance

What is it?
graphql-hooks is a hooks-first GraphQL client for React with a 22.9kB bundle, a client you construct yourself, a pluggable cache interface and optional SSR and WebSocket packages. The comparison with Apollo is in the README, and the last push was on 2026-09-01.
Who is it for?
graphql-hooks suits a React application that needs a GraphQL client and a small bundle, and that is willing to own the cache contract, because the cache is an interface you implement rather than a policy you configure. It is the wrong tool if you want the advanced cache behaviour of a full client, since the README's own comparison table lists less advanced caching configuration as the main con, and the wrong tool if you need normalised cache updates today.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 10 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • nearform/graphql-hooks1,889 ★ · +1
Star history
Repository2026-10-162026-11-07
nearform/graphql-hooks1.9k1.9k

1 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The client is a class you construct, not a provider you configure

graphql-hooks is a minimal hooks-first GraphQL client for React, and the API starts with an object you instantiate and pass down through a context.

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

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

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

The client takes a plain configuration object. url is the address of your GraphQL HTTP server, and it is required unless you enable fullWsTransport and provide a subscriptionClient. There is a fetch option that defaults to the global fetch API, a FormData option that defaults to the global FormData and needs polyfilling in Node, headers, fetchOptions, and logErrors which defaults to true.

Two options show that this is designed for real deployment rather than a demo. ssrMode is set to true when running on the server for server-side rendering, and it changes what is required: the cache object is required when ssrMode is true and optional otherwise. And subscriptionClient accepts either a SubscriptionClient instance from subscriptions-transport-ws or a Client from graphql-ws, and it also accepts a factory function, which the README gives as a way to avoid creating the client in SSR environments where the socket cannot be opened at module load.

useGETForQueries is set to true to use the HTTP GET method for all queries, with a guide of its own, and fullWsTransport is set to true to send queries and mutations over the WebSocket as well as subscriptions.

The design point is that there is no provider that owns the client for you. You build it, you hold it, and you pass it. That is what makes the same client usable in a server render and in a browser.

The cache is an interface with a reference implementation

The cache contract is the most consequential design decision in the library, and the README lays out the required methods exactly.

The cache object must provide cache.get(key), cache.set(key, data), cache.delete(key), cache.clear() and cache.keys(), plus getInitialState(). The README points to the graphql-hooks-memcache package as a reference implementation, and there is a separate package for that.

What that buys you is control. There is no built-in normalised cache with entity policies, no field-level invalidation, no optimistic layer with a reconciliation strategy. What it costs you is the same. The library asks you to implement the storage and the invalidation rules, which means an application that wants entity-level updates has to write them.

The README is candid about this in the comparison section, and the table it gives is short enough to quote. The pros are small in size, a concise API, and quick to get up and running. The con is less advanced caching configuration. In the surrounding text the authors say that if you need a client offering more customization such as advanced cache configuration, then apollo-hooks may work out to be a good choice if bundle size is not an issue, and they link a LogRocket article comparing the two.

They are also honest about performance, saying that in terms of performance this is more of a grey area as there are no official benchmarks yet. That is a notably more careful claim than most libraries make, and it is the kind of statement worth reading before you rely on either library for a performance argument.

The bundle size is the other side of the trade, and it is specific: only 22.9kB, or 7.4kB gzipped, with a bundlephobia badge in the README to track it.

Four hooks, and the loading state is part of the return value

The hooks API is the reason the library exists, and the four hooks are useQuery, useManualQuery, useMutation and useSubscription.

useQuery is the everyday one, and the shape of the return value is what makes the pattern work:

js
const { loading, error, data } = useQuery(HOMEPAGE_QUERY, {
  variables: {
    limit: 10
  }
})

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

There is no render props and no higher order component. The README lists no more render props hell as a feature, and that is the practical difference: the loading and error states are destructured alongside the data, so a component reads as a function with three branches rather than a wrapper with a callback.

useManualQuery is the one where you control when the request happens, which is what you want for a form submit or a refetch button. useMutation handles writes. useSubscription handles the WebSocket side.

The client options are richer than the two-line example above. middleware accepts an array of middleware functions with the default being none, and the README points at a middlewares readme inside the package for details. onError is a custom error handler that receives an operation object with query, variables and operationName, and a result object with data, headers and the error. There is also a request interceptors guide, an AbortController section, and a GraphQL document support section for passing a document rather than a string.

The guides in the table of contents cover SSR, pagination, file uploads, HTTP GET support, authentication, fragments, migrating from Apollo, testing and mocking, and TypeScript support. The migration guide is sectioned by component, ApolloClient to GraphQLClient, ApolloProvider to ClientContext.Provider, the Query component to useQuery, and the Mutation component to useMutation.

A monorepo with six example applications

The repository is a Lerna monorepo, and the packages directory holds the published artefacts. The main package is graphql-hooks, and the README links graphql-hooks-ssr for full server-side rendering support and graphql-hooks-memcache for plugin caching. The release list names the packages that get versioned independently, with typescript-example and subscription at 5.1.1 and persisted-queries-server at 3.0.1, all published on the same day, 2025-01-08.

The examples directory shows the range of configurations the library is expected to work in. There is create-react-app, fastify-ssr, full-ws-transport, persisted-queries, subscription and typescript.

Those six examples are more informative than the feature list, because each one isolates a hard case. fastify-ssr is the server-side rendering path with a specific framework, which is where the ssrMode flag and the getInitialState method earn their place. full-ws-transport is the configuration where queries travel over the WebSocket rather than HTTP. persisted-queries is a pattern where the server holds a registry of query strings and the client sends a hash instead of the document. subscription covers the WebSocket client. create-react-app is the plain case. And typescript covers the type checking story, which has its own check-types script in the root package.

The root package.json shows the toolchain. Tests are jest with a coverage variant, acceptance tests run through cypress open, the build is lerna run build, lint is eslint., type checking is lerna run check-types, and release is lerna publish. Formatting is prettier across js, md and ts. On top of that sits babel with the React and TypeScript presets, rollup with babel, commonjs, node-resolve and terser plugins for the bundles, and esbuild in the dev dependencies.

There is also a.husky/ directory with a prepare script that installs husky, a commitlint.config.js for conventional commits, a lint-staged setup, a.netlify/ directory for the documentation site, and an.env.sample holding a GH_TOKEN used when publishing to create a GitHub release.

Where graphql-hooks is the wrong choice

The first limit is the cache, and it is not a small one. If your application depends on normalised cache behaviour, automatic entity updates after a mutation, or a devtools view of what is in the cache, this library does not provide it and the README's comparison table says so. You would be writing that layer yourself, in the cache interface, which is work that a bigger client does for you.

The second is the absence of published benchmarks. The README says outright that there are no official benchmarks yet and that performance is a grey area. For a library whose main argument is that it is small, that is a gap in the evidence rather than a defect, but it means you are the one who has to measure it in your own application.

The third is the platform requirements. The support list is Node LTS and browsers above one percent and not dead, with FormData, Promise and fetch to be polyfilled, and the note that a custom fetch implementation can be provided instead of polyfilling, which is a good escape hatch. The native dependencies in the Node path, and the FormData requirement for file uploads, are the sort of thing that bites in a server-side rendering setup.

The fourth is the shape of the release process. Lerna with independent package versions, a separate release process document, and the last publish of the listed packages in January 2025 against a last push in September 2026 means the packages are not all moving at the same rate. If you are starting a new project, check which package version you are actually installing rather than assuming the repository's activity level applies to each artefact.

The fifth is that the library is unmaintained-looking in a specific way. The repository is not archived and the last push was on 2026-09-01, so there is activity, but the visible release history is sparse and the ecosystem around it, Apollo and graphql-ws, moves quickly.

Editorial conclusion

graphql-hooks suits a React application that needs a GraphQL client and a small bundle, and that is willing to own the cache contract, because the cache is an interface you implement rather than a policy you configure. It is the wrong tool if you want the advanced cache behaviour of a full client, since the README's own comparison table lists less advanced caching configuration as the main con, and the wrong tool if you need normalised cache updates today. Before adopting it, check that the caching features you rely on are covered by the interface, because the required methods are get, set, delete, clear and keys plus getInitialState, and implement them against a reference in graphql-hooks-memcache.

Frequently asked questions

What is graphql-hooks?

It is a minimal hooks-first GraphQL client for React, with a first-class hooks API, a bundle of only 22.9kB or 7.4kB gzipped, full SSR support through a separate package, plugin caching, and no render props.

How do I set up graphql-hooks?

Create a GraphQLClient with a url, wrap your app in ClientContext.Provider with that client as the value, and then call useQuery in child components. The package installs with npm install graphql-hooks or yarn add graphql-hooks.

What cache interface does graphql-hooks require?

The cache object needs cache.get(key), cache.set(key, data), cache.delete(key), cache.clear() and cache.keys(), plus getInitialState(). It is required when ssrMode is true and optional otherwise, and the graphql-hooks-memcache package is given as a reference implementation.

How does graphql-hooks compare with Apollo?

The README gives a short table: small in size, a concise API and quick to get up and running on the pros side, less advanced caching configuration on the cons. It also says that in terms of performance it is a grey area since there are no official benchmarks yet, and suggests apollo-hooks if you need more customization and bundle size is not an issue.

Does graphql-hooks support server-side rendering and WebSocket subscriptions?

Yes. Set ssrMode to true on the server and provide a cache, with full SSR support in the graphql-hooks-ssr package. For subscriptions, subscriptionClient accepts a SubscriptionClient from subscriptions-transport-ws or a Client from graphql-ws, and fullWsTransport sends queries and mutations over the socket too.

Official sources

  1. Issues
  2. nearform/graphql-hooks on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nearform-graphql-hooks.svg)](https://hysenlabs.com/projects/nearform-graphql-hooks)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/nearform-graphql-hooks.svg)](https://hysenlabs.com/projects/nearform-graphql-hooks)

Was this useful?