mrkeyoor.com_
Sat 08 Aug 17:40 UTC
npmWeb Frontendupdated 08 Aug 2026

@apollo/client

Apollo Client is a GraphQL client and normalized in-memory cache for JavaScript applications, with official React bindings and framework-neutral core APIs. It executes queries, mutations, subscriptions, and incremental responses, then normalizes objects so later operations and local cache reads can share data. Its main value is coordinated GraphQL state rather than HTTP transport: fetch policies, field policies, optimistic writes, pagination merges, reactive updates, and developer tools all sit on top of the operation layer.

Verdict

Apollo Client is worth its weight when a real normalized GraphQL cache coordinates a large UI. For a handful of requests or mixed REST applications, use a smaller client and avoid making cache policy a second data-modeling job.

API stability3/5Queries, mutations, links, normalized cache policies, and React hooks have long-lived concepts, and the project follows SemVer with published migration material. Version 4 nevertheless moved React exports to a subpath and requires GraphQL 16 or 17 plus RxJS 7. The repository's versioning policy also allows minor releases to change transpilation targets or supported dependency versions, which demands more upgrade testing than strict SemVer expectations suggest.
Docs5/5The official v4 site has separate guides and API pages for operations, caching, pagination, local state, errors, subscriptions, Suspense, SSR, testing, links, performance, migrations, and bundle reduction. Current examples show the v4 import paths and newer tools such as `skipToken`. The breadth is exceptional, though the many valid cache and fetch-policy combinations can make a simple answer take several pages.
Maintenance5/5Version 4.2.10 was published on August 5, 2026, the repository was pushed on August 8, 2026, and GitHub reports 415 open issues and pull requests across a 19,813-star project. The README names three maintainers, publishes a roadmap, and documents a versioning policy. The large open-work count matches a broad client with several framework and protocol surfaces rather than stale ownership.
Ecosystem5/5The package recorded 6,593,369 downloads in the latest measured week and supports React plus framework-neutral core usage, with Vue, Angular, Svelte, and other integrations around it. GraphQL Code Generator, Apollo DevTools, link packages, testing utilities, SSR integrations, and GraphQL subscriptions form a mature toolchain. The cost is an ecosystem-shaped architecture that can be excessive outside GraphQL-heavy products.

Use it if

  • Your React application has many GraphQL screens that should share a normalized entity cache
  • You need optimistic mutations, pagination field policies, subscriptions, or fine-grained cache updates
  • Your team wants mature React hooks, Suspense support, developer tools, and extensive GraphQL documentation
  • You consume a schema with stable object identifiers and can adopt generated typed documents
Skip it if

Setup reality

For Apollo Client 4, install `@apollo/client`, `graphql`, and a compatible RxJS 7 peer. React, React DOM, and websocket transports are optional peers, but a React app supplies its own compatible React 17, 18, or 19 packages. Version 4 moved React exports to `@apollo/client/react`; copying v3 examples that import `useQuery` from the package root is a common first failure. Create one `ApolloClient` with an endpoint or link plus `InMemoryCache`, then put `ApolloProvider` above every hook consumer. The hard part is cache identity. Apollo normally combines `__typename` with `id` or `_id`; objects without stable keys remain embedded, while custom identifiers need `typePolicies.keyFields`. List fields need pagination merge and `keyArgs` policies or pages overwrite each other, duplicate, or fragment into separate cache entries. Default `cache-first` behavior can look stale until fetch policies are chosen intentionally. Mutations do not magically place new list members in every cached query; refetch or update the cache and include identifiers plus `__typename` in optimistic results. Authentication, retries, uploads, persisted queries, and subscriptions require link configuration and sometimes optional packages such as `graphql-ws`. SSR and React Server Components have framework-specific integration packages and must avoid sharing one user cache across requests. The client works with any GraphQL endpoint and does not require GraphOS, but schema typing is far better with external GraphQL code generation than with hand-written result interfaces.

Patterns

Create an Apollo Client 4 instancecreate-client

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

export const client = new ApolloClient({
  link: new HttpLink({ uri: 'https://api.example.com/graphql' }),
  cache: new InMemoryCache(),
});

Install compatible `graphql` and `rxjs` peers; GraphOS is not required for a normal GraphQL endpoint.

Provide the client to React hooksprovide-react-client

import { ApolloProvider } from '@apollo/client/react';

root.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

Apollo Client 4 React exports come from `@apollo/client/react`, not the package root used by many v3 examples.

Run a GraphQL query from Reactquery-in-component

import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { id name email }
  }
`;

function User({ id }) {
  const { data, loading, error } = useQuery(GET_USER, { variables: { id } });
  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error.message}</p>;
  return <h1>{data.user.name}</h1>;
}

Include stable IDs in selections so `InMemoryCache` can normalize and share entity data across operations.

Skip a query until required input existsskip-query-safely

import { skipToken, useQuery } from '@apollo/client/react';

const result = useQuery(
  GET_USER,
  userId ? { variables: { id: userId } } : skipToken,
);

`skipToken` keeps variables type-safe and avoids inventing a placeholder ID just to satisfy TypeScript.

Run a query in response to an eventrun-lazy-query

import { useLazyQuery } from '@apollo/client/react';

function SearchButton({ term }) {
  const [search, { data, loading }] = useLazyQuery(SEARCH);
  return <button disabled={loading} onClick={() => search({ variables: { term } })}>
    {data ? `${data.search.length} results` : 'Search'}
  </button>;
}

Use lazy queries for imperative user events; ordinary render-driven data should stay declarative with `useQuery`.

Execute a mutation and refetch an active queryrun-mutation

import { gql } from '@apollo/client';
import { useMutation } from '@apollo/client/react';

const ADD_TODO = gql`
  mutation AddTodo($text: String!) { addTodo(text: $text) { id text done } }
`;

const [addTodo, state] = useMutation(ADD_TODO, {
  refetchQueries: [GET_TODOS],
});
await addTodo({ variables: { text: 'Ship it' } });

Refetching is simple but costs a network round trip, and named refetch targets only affect active queries.

Show an optimistic mutation resultoptimistic-mutation

await addTodo({
  variables: { text },
  optimisticResponse: {
    addTodo: {
      __typename: 'Todo',
      id: `temp:${crypto.randomUUID()}`,
      text,
      done: false,
    },
  },
});

Provide `__typename` and a temporary stable ID or the optimistic object cannot normalize and reconcile cleanly.

Append a mutation result to a cached listappend-cache-item

const [addTodo] = useMutation(ADD_TODO, {
  update(cache, { data }) {
    if (!data?.addTodo) return;
    cache.modify({
      fields: {
        todos(existing = [], { toReference }) {
          return [...existing, toReference(data.addTodo)];
        },
      },
    });
  },
});

A mutation result normalizes the entity but does not automatically add it to every cached list that should contain it.

Configure a custom entity keyconfigure-type-identity

const cache = new InMemoryCache({
  typePolicies: {
    Product: { keyFields: ['sku'] },
    Country: { keyFields: ['code'] },
  },
});

Only use fields that are globally stable for that GraphQL type; a changing key creates duplicate cache entities.

Merge offset-based pagination resultsmerge-offset-pages

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        feed: {
          keyArgs: ['filter'],
          merge(existing = [], incoming, { args }) {
            const merged = existing.slice();
            const offset = args?.offset ?? 0;
            incoming.forEach((item, index) => { merged[offset + index] = item; });
            return merged;
          },
        },
      },
    },
  },
});

Keep arguments that identify distinct lists in `keyArgs`; omitting a filter can merge unrelated result sets.

Fetch fresh once, then prefer cachechoose-fetch-policy

const result = useQuery(GET_DASHBOARD, {
  fetchPolicy: 'network-only',
  nextFetchPolicy: 'cache-first',
});

The default is cache-first; choose policies deliberately or stale cache data and unnecessary refetches can both look like bugs.

Run an operation outside Reactquery-without-react

const { data } = await client.query({
  query: GET_USER,
  variables: { id: '42' },
  fetchPolicy: 'network-only',
});

Use the core client in loaders and services; calling React hooks outside a component violates React's hook rules.

Alternatives

PackageRegistryPick it when
urqlnpmYou want GraphQL-focused React hooks with a smaller exchange-based client
@tanstack/react-querynpmYou want transport-agnostic server-state caching for REST, GraphQL, or mixed APIs
graphql-requestnpmYou only need a typed, minimal GraphQL request layer without a normalized cache