Skip to content

v3.9.0

Compare
Choose a tag to compare
@github-actions github-actions released this 30 Jan 21:18
· 468 commits to main since this release
9f2ccdb

Minor Changes

Memory optimizations

  • #11424 62f3b6d Thanks @phryneas! - 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.

  • #11435 5cce53e Thanks @phryneas! - 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.

  • #11254 d08970d Thanks @benjamn! - Decouple canonicalStringify from ObjectCanon for better time and memory performance.

  • #11356 cc4ac7e Thanks @phryneas! - Fix a potential memory leak in FragmentRegistry.transform and FragmentRegistry.findFragmentSpreads that would hold on to passed-in DocumentNodes for too long.

  • #11370 25e2cb4 Thanks @phryneas! - 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
  • #11389 139acd1 Thanks @phryneas! - documentTransform: use optimism and WeakCache instead of directly storing data on the Trie

  • #11358 7d939f8 Thanks @phryneas! - Fixes a potential memory leak in Concast that might have been triggered when Concast was used outside of Apollo Client.

  • #11344 bd26676 Thanks @phryneas! - Add a resetCache method to DocumentTransform and hook InMemoryCache.addTypenameTransform up to InMemoryCache.gc

  • #11367 30d17bf Thanks @phryneas! - print: use WeakCache instead of WeakMap

  • #11387 4dce867 Thanks @phryneas! - QueryManager.transformCache: use WeakCache instead of WeakMap

  • #11369 2a47164 Thanks @phryneas! - 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
  • #10804 221dd99 Thanks @phryneas! - use WeakMap in React Native with Hermes

  • #11355 7d8e184 Thanks @phryneas! - InMemoryCache.gc now also triggers FragmentRegistry.resetCaches (if there is a FragmentRegistry)

  • #11409 2e7203b Thanks @phryneas! - Adds an experimental ApolloClient.getMemoryInternals helper

  • #11343 776631d Thanks @phryneas! - Add reset method to print, hook up to InMemoryCache.gc

Suspense-enabled data fetching on user interaction with useLoadableQuery

  • #11300 a815873 Thanks @jerelmiller! - 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

  • #11412 58db5c3 Thanks @jerelmiller! - 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.

Testing utility improvements

  • #11178 4d64a6f Thanks @sebakerckhof! - Support re-using of mocks in the MockedProvider

  • #6701 8d2b4e1 Thanks @prowe! - 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

  • #11412 58db5c3 Thanks @jerelmiller! - 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

  • #11410 07fcf6a Thanks @sf-twingate! - 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.

    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
    }

Network adapters for multipart subscriptions usage with Relay and urql

  • #11301 46ab032 Thanks @alessbell! - 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

  • #11397 3f7eecb Thanks @aditya-kumawat! - 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

  • #11345 1759066 Thanks @phryneas!

    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

  • #11202 7c2bc08 Thanks @benjamn! - Prevent QueryInfo#markResult mutation of result.data and return cache data consistently whether complete or incomplete.

  • #11442 4b6f2bc Thanks @jerelmiller! - 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.

  • #11438 6d46ab9 Thanks @jerelmiller! - 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.

  • #11175 d6d1491 Thanks @phryneas! - 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.

  • #11495 1190aa5 Thanks @jerelmiller! - Increase the default memory limits for executeSelectionSet and executeSelectionSetArray.

Patch Changes

  • #11275 3862f9b Thanks @phryneas! - 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;
    }
  • #11443 ff5a332 Thanks @phryneas! - 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.

  • #11385 d9ca4f0 Thanks @phryneas! - ensure defaultContext is also used for mutations and subscriptions

  • #11503 67f62e3 Thanks @jerelmiller! - Release changes from v3.8.10

  • #11078 14edebe Thanks @phryneas! - ObservableQuery: prevent reporting results of previous queries if the variables changed since

  • #11439 33454f0 Thanks @jerelmiller! - 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.

  • #11371 ebd8fe2 Thanks @phryneas! - Clarify types of EntityStore.makeCacheKey.