Packages

@apollo/client

4.3.5 · 19 Oct 2026MIT5.3M / weekRepositoryHomepagenpmAtom feed

Your version

3.9.0

30 Jan 2024Where it was published

3.14.1 is the newest release of 3.x.

In short

2 deprecated28 new5 fixed

  • deprecated

    Deprecates canonizeResults.

  • deprecated

    Adds a deprecation warning to the HOC and render prop APIs.

Release note

Minor Changes

Memory optimizations

  • Simplify RetryLink, fix potential memory leak

    Historically, RetryLink would keep a values array of all previous values, in case the operation would get an additional subscriber at a later point in time.

    In practice, this could lead to a memory leak (#11393) and did not serve any further purpose, as the resulting observable would only be subscribed to by Apollo Client itself, and only once - it would be wrapped in a Concast before being exposed to the user, and that Concast would handle subscribers on its own.

  • deprecated

    Deprecates canonizeResults.

    Using canonizeResults can result in memory leaks so we generally do not recommend using this option anymore. A future version of Apollo Client will contain a similar feature without the risk of memory leaks.

  • Decouple canonicalStringify from ObjectCanon for better time and memory performance.

  • Fix a potential memory leak in FragmentRegistry.transform and FragmentRegistry.findFragmentSpreads that would hold on to passed-in DocumentNodes for too long.

  • parse function: improve memory management

    • use LRU WeakCache instead of Map to keep a limited number of parsed results

    • cache is initiated lazily, only when needed

    • expose parse.resetCache() method

  • documentTransform: use optimism and WeakCache instead of directly storing data on the Trie

  • Fixes a potential memory leak in Concast that might have been triggered when Concast was used outside of Apollo Client.

  • Add a resetCache method to DocumentTransform and hook InMemoryCache.addTypenameTransform up to InMemoryCache.gc

  • print: use WeakCache instead of WeakMap

  • QueryManager.transformCache: use WeakCache instead of WeakMap

  • Persisted Query Link: Improve memory management

    • use LRU WeakCache instead of WeakMap to keep a limited number of hash results

    • hash cache is initiated lazily, only when needed

    • expose persistedLink.resetHashCache() method

    • reset hash cache if the upstream server reports it doesn't accept persisted queries

  • Use WeakMap in React Native with Hermes

  • InMemoryCache.gc now also triggers FragmentRegistry.resetCaches (if there is a FragmentRegistry)

  • Adds an experimental ApolloClient.getMemoryInternals helper

  • Add reset method to print, hook up to InMemoryCache.gc

Suspense-enabled data fetching on user interaction with useLoadableQuery

  • Introduces a new useLoadableQuery hook. This hook works similarly to useBackgroundQuery in that it returns a queryRef that can be used to suspend a component via the useReadQuery hook. It provides a more ergonomic way to load the query during a user interaction (for example when wanting to preload some data) that would otherwise be clunky with useBackgroundQuery.

    function App() {
      const [loadQuery, queryRef, { refetch, fetchMore, reset }] =
        useLoadableQuery(query, options);
    
      return (
        <>
          <button onClick={() => loadQuery(variables)}>Load query</button>
          <Suspense fallback={<SuspenseFallback />}>
            {queryRef && <Child queryRef={queryRef} />}
          </Suspense>
        </>
      );
    }
    
    function Child({ queryRef }) {
      const { data } = useReadQuery(queryRef);
    
      // ...
    }

Begin preloading outside of React with createQueryPreloader

  • Add the ability to start preloading a query outside React to begin fetching as early as possible. Call createQueryPreloader to create a preloadQuery function which can be called to start fetching a query. This returns a queryRef which is passed to useReadQuery and suspended until the query is done fetching.

    const preloadQuery = createQueryPreloader(client);
    const queryRef = preloadQuery(QUERY, { variables, ...otherOptions });
    
    function App() {
      return {
        <Suspense fallback={<div>Loading</div>}>
          <MyQuery />
        </Suspense>
      }
    }
    
    function MyQuery() {
      const { data } = useReadQuery(queryRef);
    
      // do something with data
    }

Testing utility improvements

  • Support re-using of mocks in the MockedProvider

  • Ability to dynamically match mocks

    Adds support for a new property MockedResponse.variableMatcher: a predicate function that accepts a variables param. If true, the variables will be passed into the ResultFunction to help dynamically build a response.

New useQueryRefHandlers hook

  • Create a new useQueryRefHandlers hook that returns refetch and fetchMore functions for a given queryRef. This is useful to get access to handlers for a queryRef that was created by createQueryPreloader or when the handlers for a queryRef produced by a different component are inaccessible.

    const MyComponent({ queryRef }) {
      const { refetch, fetchMore } = useQueryRefHandlers(queryRef);
    
      // ...
    }

Bail out of optimisticResponse updates with the IGNORE sentinel object

  • Allow returning IGNORE sentinel object from optimisticResponse functions to bail-out from the optimistic update.

    Consider this example:

    const UPDATE_COMMENT = gql`
      mutation UpdateComment($commentId: ID!, $commentContent: String!) {
        updateComment(commentId: $commentId, content: $commentContent) {
          id
          __typename
          content
        }
      }
    `;
    
    function CommentPageWithData() {
      const [mutate] = useMutation(UPDATE_COMMENT);
      return (
        <Comment
          updateComment={({ commentId, commentContent }) =>
            mutate({
              variables: { commentId, commentContent },
              optimisticResponse: (vars, { IGNORE }) => {
                if (commentContent === "foo") {
                  // conditionally bail out of optimistic updates
                  return IGNORE;
                }
                return {
                  updateComment: {
                    id: commentId,
                    __typename: "Comment",
                    content: commentContent,
                  },
                };
              },
            })
          }
        />
      );
    }

    The IGNORE sentinel can be destructured from the second parameter in the callback function signature passed to optimisticResponse.

Network adapters for multipart subscriptions usage with Relay and urql

  • Add multipart subscription network adapters for Relay and urql

Relay

import { createFetchMultipartSubscription } from "@apollo/client/utilities/subscriptions/relay";
import { Environment, Network, RecordSource, Store } from "relay-runtime";

const fetchMultipartSubs = createFetchMultipartSubscription(
  "http://localhost:4000"
);

const network = Network.create(fetchQuery, fetchMultipartSubs);

export const RelayEnvironment = new Environment({
  network,
  store: new Store(new RecordSource()),
});

Urql

import { createFetchMultipartSubscription } from "@apollo/client/utilities/subscriptions/urql";
import { Client, fetchExchange, subscriptionExchange } from "@urql/core";

const url = "http://localhost:4000";

const multipartSubscriptionForwarder = createFetchMultipartSubscription(url);

const client = new Client({
  url,
  exchanges: [
    fetchExchange,
    subscriptionExchange({
      forwardSubscription: multipartSubscriptionForwarder,
    }),
  ],
});

skipPollAttempt callback function

  • Adds a new skipPollAttempt callback function that's called whenever a refetch attempt occurs while polling. If the function returns true, the refetch is skipped and not reattempted until the next poll interval. This will solve the frequent use-case of disabling polling when the window is inactive.

    useQuery(QUERY, {
      pollInterval: 1000,
      skipPollAttempt: () => document.hidden, // or !document.hasFocus()
    });
    // or define it globally
    new ApolloClient({
      defaultOptions: {
        watchQuery: {
          skipPollAttempt: () => document.hidden, // or !document.hasFocus()
        },
      },
    });

QueryManager.inFlightLinkObservables now uses a strong Trie as an internal data structure

Warning: requires @apollo/experimental-nextjs-app-support update

If you are using @apollo/experimental-nextjs-app-support, you will need to update that to at least 0.5.2, as it accesses this internal data structure.

More Minor Changes

  • Prevent QueryInfo#markResult mutation of result.data and return cache data consistently whether complete or incomplete.

  • Remove the need to call retain from useLoadableQuery since useReadQuery will now retain the query. This means that a queryRef that is not consumed by useReadQuery within the given autoDisposeTimeoutMs will now be auto diposed for you.

    Thanks to #11412, disposed query refs will be automatically resubscribed to the query when consumed by useReadQuery after it has been disposed.

  • Remove the need to call retain from useBackgroundQuery since useReadQuery will now retain the query. This means that a queryRef that is not consumed by useReadQuery within the given autoDisposeTimeoutMs will now be auto diposed for you.

    Thanks to #11412, disposed query refs will be automatically resubscribed to the query when consumed by useReadQuery after it has been disposed.

  • To work around issues in React Server Components, especially with bundling for
    the Next.js "edge" runtime we now use an external package to wrap react imports
    instead of importing React directly.

  • Increase the default memory limits for executeSelectionSet and executeSelectionSetArray.

Patch Changes

  • Add a defaultContext option and property on ApolloClient, e.g. for keeping track of changing auth tokens or dependency injection.

    This can be used e.g. in authentication scenarios, where a new token might be generated outside of the link chain and should passed into the link chain.

    import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
    import { setContext } from "@apollo/client/link/context";
    
    const httpLink = createHttpLink({
      uri: "/graphql",
    });
    
    const authLink = setContext((_, { headers, token }) => {
      return {
        headers: {
          ...headers,
          authorization: token ? `Bearer ${token}` : "",
        },
      };
    });
    
    const client = new ApolloClient({
      link: authLink.concat(httpLink),
      cache: new InMemoryCache(),
    });
    
    // somewhere else in your application
    function onNewToken(newToken) {
      // token can now be changed for future requests without need for a global
      // variable, scoped ref or recreating the client
      client.defaultContext.token = newToken;
    }
  • deprecated

    Adds a deprecation warning to the HOC and render prop APIs.

    The HOC and render prop APIs have already been deprecated since 2020,
    but we previously didn't have a @deprecated tag in the DocBlocks.

  • Ensure defaultContext is also used for mutations and subscriptions

  • Release changes from v3.8.10

  • ObservableQuery: Prevent reporting results of previous queries if the variables changed since

  • Address bundling issue introduced in #11412 where the react/cache internals ended up duplicated in the bundle. This was due to the fact that we had a react/hooks entrypoint that imported these files along with the newly introduced createQueryPreloader function, which lived outside of the react/hooks folder.

  • Clarify types of EntityStore.makeCacheKey.