github apollographql/apollo-client @apollo/client@4.0.0

latest releases: @apollo/client@4.0.4, @apollo/client@4.0.3, @apollo/client@4.0.2...
14 days ago

Apollo Client 4.0 Release Notes

Apollo Client 4.0 delivers a more modern, efficient, and type-safe GraphQL client experience through various architectural improvements and API refinements. This release focuses on developer experience, bundle size optimization, and framework flexibility.

Key Improvements

🎯 Framework-Agnostic Core

Apollo Client 4.0 separates React functionality from the core library, making @apollo/client truly framework-agnostic. React exports now live in @apollo/client/react, allowing developers to use Apollo Client with any JavaScript framework without React dependencies.

📦 Smaller Bundle Sizes

  • Opt-in Local State Management: The @client directive functionality is now opt-in via the LocalState class, reducing bundle size when not using local state
  • Modern Build Target: Transpiled to target since 2023, node >= 20, not dead, leveraging modern JavaScript features for better performance
  • Improved Tree-Shaking: Proper exports field in package.json enables better dead code elimination

💥 Unified Error Handling

Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:

  • ApolloError removed in favor of specific error classes
  • Unification of errors to a single error property
  • Network errors now respect errorPolicy settings
  • External errors passed through without wrapping
  • New, more granular error classes with static .is() methods for robust type narrowing

🔧 Enhanced TypeScript Support

  • Namespaced Types: Types are now colocated with their APIs (e.g., useQuery.Options instead of QueryHookOptions)
  • Precise Return Types: Return types accurately reflect the options passed (e.g., returnPartialData makes data type DeepPartial<TData>)
  • Stricter Type Safety: Required variables are now enforced more consistently throughout the client
  • New dataState Property: Enables accurate type narrowing of query results
  • Module Augmentation: Custom context types via declaration merging instead of fragile generics
  • Customizable Type Implementations: Select types can now be customized to provide your own type implementation to seamlessly integrate with external tools such as GraphQL Codegen or gql.tada

⚡ Modern Observable Implementation

Apollo Client 4.0 migrates from zen-observable to RxJS, providing the industry-standard Observable implementation backed by a rich ecosystem of utilities.

Major Features

Unified Error Handling

Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:

Key Changes:

  • ApolloError removed in favor of specific error classes
  • Network errors now respect errorPolicy settings
  • External errors passed through without wrapping
  • New error classes with static .is() methods for type checking

Error Classes:

  • CombinedGraphQLErrors - GraphQL errors from the server
  • ServerError - Non-GraphQL server errors
  • ServerParseError - Server response parsing errors
  • UnconventionalError - Wrapper for non-error thrown values
  • LinkError - Errors from the link chain (via .is() check)

Migration Example:

// Apollo Client 3
if (error instanceof ApolloError) {
  console.log(error.graphQLErrors);
  console.log(error.networkError);
}

// Apollo Client 4
import { CombinedGraphQLErrors } from "@apollo/client";

if (CombinedGraphQLErrors.is(error)) {
  console.log(error.errors); // GraphQL errors
} else if (error) {
  console.log(error.message); // Other errors
}

The dataState Property

A new property that clearly indicates the completeness of query results:

Values:

  • empty - No data available (data is undefined)
  • partial - Incomplete data from cache when returnPartialData is true
  • streaming - Incomplete data from a deferred query still streaming
  • complete - Fully satisfied query result

Benefits:

  • Accurate TypeScript type narrowing
  • Clear loading state distinction
  • Better handling of partial results
const { data, dataState } = useQuery(MY_QUERY);

if (dataState === "complete") {
  // TypeScript knows data is fully populated
  console.log(data.allFields);
} else if (dataState === "partial") {
  // TypeScript knows data might be missing fields
  console.log(data?.someField);
}

Pluggable Incremental Delivery (@defer Support)

Apollo Client 4.0 makes incremental delivery configurable and future-proof:

import { Defer20220824Handler } from "@apollo/client/incremental";

const client = new ApolloClient({
  // ...
  incrementalHandler: new Defer20220824Handler(),
});

Available Handlers:

  • NotImplementedHandler - Default, throws if @defer is used
  • Defer20220824Handler - Apollo Router format support (also aliased as GraphQL17Alpha2Handler)

Local State Management Improvements

Local state is now opt-in via the LocalState class:

import { LocalState } from "@apollo/client/local-state";

const client = new ApolloClient({
  cache,
  localState: new LocalState({
    resolvers: {
      Query: {
        myField: () => "Hello World",
      },
    },
  }),
});

Resolver Context Changes:

// Apollo Client 3
const resolver = (parent, args, context, info) => {
  const { cache } = context;
};

// Apollo Client 4
const resolver = (parent, args, context, info) => {
  const { client, requestContext, phase } = context;
  const cache = client.cache;
};

React-Specific Improvements

More Predictable Hooks

useLazyQuery Overhaul:

  • No longer accepts variables or context options (pass to execute instead)
  • execute function only accepts variables and context
  • Cannot be called during render or SSR
  • Automatic cancellation of in-flight queries when new ones start

useMutation Changes:

  • Removed ignoreResults option - use client.mutate directly for fire-and-forget mutations

useQuery Changes:

  • notifyOnNetworkStatusChange now defaults to true
  • Removed deprecated onCompleted and onError callbacks

New SSR API

The new prerenderStatic API replaces deprecated SSR functions:

import { prerenderStatic } from "@apollo/client/react/ssr";

// Works with React 19's prerender APIs
const html = await prerenderStatic(<App />, {
  client,
});

React Compiler Support

Pre-compiled React hooks optimized by the React Compiler:

// Use compiled hooks for potential performance improvements
import { useQuery } from "@apollo/client/react/compiled";

The compiled hooks are built with React Compiler v19.1.0-rc.2 and include a runtime polyfill for compatibility with React 17+.

Link System Evolution

All Links Now Classes

Migration from creator functions to classes:

// Apollo Client 3
import { createHttpLink, setContext } from "@apollo/client";
const httpLink = createHttpLink({ uri: "/graphql" });
const authLink = setContext((operation, prevContext) => {
  /*...*/
});

// Apollo Client 4
import { HttpLink, SetContextLink } from "@apollo/client";
const httpLink = new HttpLink({ uri: "/graphql" });
const authLink = new SetContextLink((prevContext, operation) => {
  /*...*/
});

ErrorLink Changes

// Apollo Client 3
onError(({ graphQLErrors, networkError }) => {
  // Handle errors separately
});

// Apollo Client 4
new ErrorLink(({ error }) => {
  if (CombinedGraphQLErrors.is(error)) {
    // Handle GraphQL errors
  } else if (error) {
    // Handle other errors
  }
});

Migration Tools

Automated Codemod

Apollo Client 4.0 provides a comprehensive codemod to automate migration:

# Basic usage
npx @apollo/client-codemod-migrate-3-to-4 src

# TypeScript projects (run separately)
npx @apollo/client-codemod-migrate-3-to-4 --parser ts --extensions ts src
npx @apollo/client-codemod-migrate-3-to-4--parser tsx --extensions tsx src

The codemod handles:

  1. Import updates - Moves React imports to @apollo/client/react
  2. Type migrations - Updates types to new namespaced locations
  3. Link updates - Converts creator functions to classes
  4. Removed exports - Moves to @apollo/client/v4-migration with migration instructions

Breaking Changes Summary

Installation

# RxJS is now a peer dependency
npm install @apollo/client graphql rxjs

ApolloClient Constructor

  • link option is now required (no more implicit HttpLink creation)
  • uri, headers, credentials removed - use HttpLink directly
  • name and version moved to clientAwareness option
  • resolvers moved to LocalState constructor
  • connectToDevTools replaced with devtools.enabled
  • disableNetworkFetches renamed to prioritizeCacheValues

Type System

  • Removed TContext and TCacheShape generics
  • Types moved to namespaces (see migration guide for full list)
  • Custom context via module augmentation

Observable Changes

  • Requires calling .pipe() for transformations
  • Use RxJS operators instead of method chaining

Testing

  • MockedProvider now has realistic delays by default (20-50ms)
  • createMockClient removed - use MockLink directly

Performance & Build Improvements

  • Modern JavaScript: No downlevel transpilation for modern features
  • No Polyfills: Cleaner bundles, bring your own if needed
  • Development Mode: Controlled via export conditions, not global __DEV__
  • ESM Support: Proper exports field for better module resolution
  • Source Maps: Fixed and improved for better debugging

Deprecations & Removals

Removed Packages/Exports

  • React render prop components (@apollo/client/react/components)
  • Higher-order components (@apollo/client/react/hoc)
  • @apollo/client/react/parser
  • @apollo/client/utilities/globals

Upgrade Path

  1. Update to Apollo Client 3.14 first for deprecation warnings
  2. Install peer dependencies: npm install rxjs
  3. Run the codemod to automate import and type updates
  4. Update ApolloClient initialization (explicit HttpLink, LocalState if needed)
  5. Review error handling - update to use new error classes
  6. Test thoroughly - especially SSR, error handling, and local state

Resources

Acknowledgments

Apollo Client 4.0 represents years of community feedback and contributions. Thank you to all our contributors, early adopters, and the entire GraphQL community for making this release possible.

Major Changes

  • #12644 fe2f005 Thanks @jerelmiller! - Replace the result property on ServerError with bodyText. bodyText is set to the raw string body. HttpLink and BatchHttpLink no longer try and parse the response body as JSON when a ServerError is thrown.

  • #12673 cee90ab Thanks @phryneas! - The includeExtensions option of HttpLink and BatchHttpLink now defaults
    to true.

    If includeExtensions is true, but extensions is not set or empty, extensions
    will not be included in outgoing requests.

  • #12686 dc4b1d0 Thanks @jerelmiller! - A @defer query that has not yet finished streaming is now considered loading and thus the loading flag will be true until the response has completed. A new NetworkStatus.streaming value has been introduced and will be set as the networkStatus while the response is streaming.

  • #12539 dd0d6d6 Thanks @jerelmiller! - onError link now uses a single error property to report the error that caused the link callback to be called. This will be an instance of CombinedGraphQLErrors in the event GraphQL errors were emitted from the terminating link, CombinedProtocolErrors if the terminating link emitted protocol errors, or the unwrapped error type if any other non-GraphQL error was thrown or emitted.

    - const errorLink = onError(({ graphQLErrors, networkError, protocolErrors }) => {
    -   graphQLErrors.forEach(error => console.log(error.message));
    + const errorLink = onError(({ error }) => {
    +   if (error.name === 'CombinedGraphQLErrors') {
    +     error.errors.forEach(rawError => console.log(rawError.message));
    +   }
    });
  • #12586 605db8e Thanks @jerelmiller! - Remove the typeDefs option from ApolloClient.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Remove the asyncMap utility function. Instead use one of the RxJS operators that creates Observables from promises, such as from.

  • #12398 8cf5077 Thanks @jerelmiller! - Removes the isApolloError utility function to check if the error object is an ApolloError instance. Use instanceof to check for more specific error types that replace ApolloError.

  • #12379 ef892b4 Thanks @jerelmiller! - Removes the addTypename option from InMemoryCache and MockedProvider. __typename is now always added to the outgoing query document when using InMemoryCache and cannot be disabled.

    If you are using <MockedProvider /> with addTypename={false}, ensure that your mocked responses include a __typename field. This will ensure cache normalization kicks in and behaves more like production.

  • #12396 00f3d0a Thanks @jerelmiller! - Remove the deprecated errors property from useQuery and useLazyQuery. Read errors from the error property instead.

  • #12809 e2a0be8 Thanks @jerelmiller! - operation.getContext now returns a Readonly<OperationContext> type.

  • #12809 e2a0be8 Thanks @jerelmiller! - The ApolloLink.Request (i.e. GraphQLRequest) passed to ApolloLink.execute no longer accepts operationName and operationType options. These properties are derived from the query and set on the returned ApolloLink.Operation type.

  • #12712 bbb2b61 Thanks @jerelmiller! - An error is now thrown when trying to call fetchMore on a cache-only query.

  • #12222 d1a9054 Thanks @jerelmiller! - Drop support for React 16.

  • #12787 8ce31fa Thanks @phryneas! - Remove DataProxy namespace and interface.

  • #12450 876d070 Thanks @jerelmiller! - Remove TSerialized generic argument to ApolloCache. The ApolloCache base cache abstraction now returns unknown for cache.extract which can be overridden by a cache subclass.

  • #12614 d2851e2 Thanks @jerelmiller! - The getCacheKey function is no longer available from operation.getContext() in the link chain. Use operation.client.cache.identify(obj) in the link chain instead.

  • #12376 a0c996a Thanks @jerelmiller! - Remove deprecated ignoreResults option from useMutation. If you don't want to synchronize component state with the mutation, use useApolloClient to access your client instance and use client.mutate directly.

  • #12644 fe2f005 Thanks @jerelmiller! - More strictly adhere to the GraphQL over HTTP spec. This change adds support for the application/graphql-response+json media type and modifies the behavior of the application/json media type.

    • The client will parse the response as a well-formed GraphQL response when the server encodes content-type using application/graphql-response+json with a non-200 status code.
    • The client will now throw a ServerError when the server encodes content-type using application/json and returns a non-200 status code.
    • The client will now throw a ServerError when the server encodes using any other content-type and returns a non-200 status code.

    NOTE: If you use a testing utility to mock requests in your test, you may experience different behavior than production if your testing utility responds as application/json but your production server responds as application/graphql-response+json. If a content-type header is not set, the client interprets the response as application/json.

  • #12600 34ff6aa Thanks @jerelmiller! - Move most of the utilities in @apollo/client/utilities to @apollo/client/utilities/internal. Many of the utilities exported from the @apollo/client/utilities endpoint were not considered stable.

    As a result of this change, utilities or types exported from @apollo/client/utilities are now documented and considered stable and will not undergo breaking changes.

  • #12513 9c3207c Thanks @phryneas! - Removed the @apollo/client/react/context and @apollo/client/react/hooks entry points. Please use @apollo/client/react instead.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Unusubscribing from ObservableQuery while a request is in flight will no longer terminate the request by unsubscribing from the link observable.

  • #12463 3868df8 Thanks @jerelmiller! - ObservableQuery.setOptions has been removed as it was an alias of reobserve. Prefer using reobserve directly instead.

    const observable = client.watchQuery(options);
    
    // Use reobserve to set new options and reevaluate the query
    - observable.setOptions(newOptions);
    + observable.reobserve(newOptions);

    As a result of this change, reobserve has been marked for public use and is no longer considered an internal API. The newNetworkStatus argument has been removed to facilitate this change.

  • #12478 5ea6a45 Thanks @jerelmiller! - Remove variables from the result returned from useSubscription.

  • #12735 5159880 Thanks @jerelmiller! - Remove deprecated resultCacheMaxSize option from InMemoryCache options.

  • #12673 cee90ab Thanks @phryneas! - The ApolloClient constructor options name and version that are used to
    configure the client awareness feature have moved onto a clientAwareness key.

    const client = new ApolloClient({
      // ..
    -  name: "my-app",
    -  version: "1.0.0",
    +  clientAwareness: {
    +    name: "my-app",
    +    version: "1.0.0",
    +  },
    });
  • #12367 e6af35e Thanks @jerelmiller! - The previousData property on useLazyQuery will now change only when data changes. Previously previousData would change to the same value as data while the query was loading.

  • #12690 5812759 Thanks @phryneas! - Aliasing any other field to __typename is now forbidden.

  • #12556 c3fceda Thanks @phryneas! - ObservableQuery will now keep previous data around when emitting a loading state, unless query or variables changed.
    Note that @exports variables are not taken into account for this, so data will stay around even if they change.

  • #12776 bce9b74 Thanks @jerelmiller! - Report masked fragments as complete even when a nested masked fragment contains partial data.

  • #12788 4179446 Thanks @phryneas! - TVariables now always extends OperationVariables in all interfaces.

  • #12224 51e6c0f Thanks @jerelmiller! - Remove deprecated partialRefetch option.

  • #12407 8b1390b Thanks @jerelmiller! - Calling refetch with new variables will now set the networkStatus to refetch instead of setVariables.

  • #12476 6afff60 Thanks @jerelmiller! - Subscriptions now emit a SubscribeResult instead of a FetchResult. As a result, the errors field has been removed in favor of error.

  • #12457 32e85ea Thanks @jerelmiller! - Network errors triggered by queries now adhere to the errorPolicy. This means that GraphQL errors and network errors now behave the same way. Previously promise-based APIs, such as client.query, would reject the promise with the network error even if errorPolicy was set to ignore. The promise is now resolved with the error property set to the network error instead.

  • #12840 83e132a Thanks @phryneas! - If you use an incremental delivery handler, you now have to explicitly opt into adding the chunk types to the ApolloLink.Result type.

    import { Defer20220824Handler } from "@apollo/client/incremental";
    
    declare module "@apollo/client" {
      export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {}
    }
  • #12712 bbb2b61 Thanks @jerelmiller! - cache-only queries are no longer refetched when calling client.reFetchObservableQueries when includeStandby is true.

  • #12808 8e31a23 Thanks @phryneas! - HTTP Multipart handling will now throw an error if the connection closed before the final boundary has been received.
    Data after the final boundary will be ignored.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Remove the iterateObserversSafely utility function.

  • #12825 292b949 Thanks @jerelmiller! - The serializeFetchParameter helper is no longer exported and JSON.stringify is used directly. As such, the ClientParseError type has also been removed in favor of throwing any JSON serialize errors directly.

  • #12595 60bb49c Thanks @jerelmiller! - Remove the @apollo/client/testing/experimental test utilities. Use GraphQL Testing Library instead.

  • #12718 ecfc02a Thanks @jerelmiller! - Version bump only to release latest as rc.

  • #12470 d32902f Thanks @phryneas! - ssrMode, ssrForceFetchDelay and disableNetworkFetches have been reworked:

    Previously, a ObservableQuery created by client.query or client.watchQuery
    while one of those were active would permanently be changed from a fetchPolicy
    of "network-only" or "cache-and-network" to "cache-first", and stay that way
    even long after disableNetworkFetches would have been deactivated.

    Now, the ObservableQuery will keep their original fetchPolicy, but queries
    made during disableNetworkFetches will just apply the fetchPolicy replacement
    at request time, just for that one request.

    ApolloClient.disableNetworkFetches has been renamed to ApolloClient.prioritizeCacheValues to better reflect this behaviour.

  • #12559 49ace0e Thanks @jerelmiller! - ObservableQuery.variables can now be reset back to empty when calling reobserve with variables: undefined. Previously the variables key would be ignored so variables would remain unchanged.

  • #12559 49ace0e Thanks @jerelmiller! - never is no longer supported as a valid TVariables generic argument for APIs that require variables as part of its type. Use Record<string, never> instead.

  • #12735 5159880 Thanks @jerelmiller! - Remove deprecated connectToDevtools option from ApolloClientOptions. Use devtools.enabled instead.

  • #12576 a92ff78 Thanks @jerelmiller! - The cache and forceFetch properties are no longer available on context when calling operation.getContext(). cache can be accessed through the operation with operation.client.cache instead. forceFetch has been replaced with queryDeduplication which specifies whether queryDeduplication was enabled for the request or not.

  • #12533 73221d8 Thanks @jerelmiller! - Remove the onError and setOnError methods from ApolloLink. onError was only used by MockLink to rewrite errors if setOnError was used.

  • #12485 d338303 Thanks @jerelmiller! - Throw an error for queries and mutations if the link chain completes without emitting a value.

  • #12556 c3fceda Thanks @phryneas! - Removed getLastResult, getLastError and resetLastResults from ObservableQuery

  • #12663 01512f2 Thanks @jerelmiller! - Unsubscribing from an ObservableQuery before a value has been emitted will remove the query from the tracked list of queries and will no longer be eligible for query deduplication.

  • #12809 e2a0be8 Thanks @jerelmiller! - operation.operationType is now a non-null OperationTypeNode. It is now safe to compare this value without having to check for undefined.

  • #12398 8cf5077 Thanks @jerelmiller! - Apollo Client no longer wraps errors in ApolloError. ApolloError has been replaced with separate error classes depending on the cause of the error. As such, APIs that return an error property have been updated to use the generic Error type. Use instanceof to check for more specific error types.

    Migration guide

    ApolloError encapsulated 4 main error properties. The type of error would determine which property was set:

    • graphqlErrors - Errors returned from the errors field by the GraphQL server
    • networkError - Any non-GraphQL error that caused the query to fail
    • protocolErrors - Transport-level errors that occur during multipart HTTP subscriptions
    • clientErrors - A space to define custom errors. Mostly unused.

    These errors were mutally exclusive, meaning both networkError and graphqlErrors were never set simultaneously. The following replaces each of these fields from ApolloError.

    graphqlErrors

    GraphQL errors are now encapsulated in a CombinedGraphQLErrors instance. You can access the raw GraphQL errors via the errors property.

    import { CombinedGraphQLErrors } from "@apollo/client";
    
    // ...
    
    const { error } = useQuery(query);
    
    if (error && error instanceof CombinedGraphQLErrors) {
      console.log(error.errors);
    }

    networkError

    Network errors are no longer wrapped and are instead passed through directly.

    const client = new ApolloClient({
      link: new ApolloLink(() => {
        return new Observable((observer) => {
          observer.error(new Error("Test error"));
        });
      }),
    });
    
    // ...
    
    const { error } = useQuery(query);
    
    // error is `new Error('Test error')`;

    protocolErrors

    Protocol errors are now encapsulated in a CombinedProtocolErrors instance. You can access the raw protocol errors via the errors property.

    import { CombinedProtocolErrors } from "@apollo/client";
    
    // ...
    
    const { error } = useSubscription(subscription);
    
    if (error && error instanceof CombinedProtocolErrors) {
      console.log(error.errors);
    }

    clientErrors

    These were unused by the client and have no replacement. Any non-GraphQL or non-protocol errors are now passed through unwrapped.

    Strings as errors

    If the link sends a string error, Apollo Client will wrap this in an Error instance. This ensures error properties are guaranteed to be of type Error.

    const client = new ApolloClient({
      link: new ApolloLink(() => {
        return new Observable((observer) => {
          // Oops we sent a string instead of wrapping it in an `Error`
          observer.error("Test error");
        });
      }),
    });
    
    // ...
    
    const { error } = useQuery(query);
    
    // The error string is wrapped and returned as `new Error('Test error')`;

    Non-error types

    If the link chain sends any other object type as an error, Apollo Client will wrap this in an UnknownError instance with the cause set to the original object. This ensures error properties are guaranteed to be of type Error.

    const client = new ApolloClient({
      link: new ApolloLink(() => {
        return new Observable((observer) => {
          observer.error({ message: "Not a proper error type" });
        });
      }),
    });
    
    // ...
    
    const { error } = useQuery(query);
    
    // error is an `UnknownError` instance. error.cause returns the original object.
  • #12809 e2a0be8 Thanks @jerelmiller! - operation.operationName is now set as string | undefined where undefined represents an anonymous query. Previously operationName would return an empty string as the operationName for anonymous queries.

  • #12450 876d070 Thanks @jerelmiller! - Remove the TCacheShape generic argument to ApolloClient. client.extract() now returns unknown by default. You will either need to type-cast this to the expected serialized shape, or use the cache.extract() directly from the subclass to get more specific types.

  • #12774 511b4f3 Thanks @jerelmiller! - Apply document transforms before reading data from the cache for client.readQuery, client.readFragment, client.watchFragment, useFragment, and useSuspenseFragment.

    NOTE: This change does not affect the equivalent cache.* APIs. To read data from the cache without first running document transforms, run cache.readQuery, cache.readFragment, etc.

  • #12705 a60f411 Thanks @jerelmiller! - cache-only queries will now initialize with loading: false and networkStatus: NetworkStatus.ready when there is no data in the cache.

    This means useQuery will no longer render a short initial loading state before rendering loading: false and ObservableQuery.getCurrentResult() will now return loading: false immediately.

  • #12475 3de63eb Thanks @jerelmiller! - Unify error behavior on mutations for GraphQL errors and network errors by ensuring network errors are subject to the errorPolicy. Network errors created when using an errorPolicy of all will now resolve the promise and be returned on the error property of the result, or stripped away when the errorPolicy is none.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Remove fromError utility function. Use throwError instead.

  • #12649 0be92ad Thanks @jerelmiller! - The TData generic provided to types that return a dataState property is now modified by the given DataState generic instead of passing a modified TData type. For example, a QueryRef that could return partial data was defined as QueryRef<DeepPartial<TData>, TVariables>. Now TData should be provided unmodified and a set of allowed states should be given instead: QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'>.

    To migrate, use the following guide to replace your type with the right set of states (all types listed below are changed the same way):

    - QueryRef<TData, TVariables>
    // `QueryRef`'s default is 'complete' | 'streaming' so this can also be left alone if you prefer
    // All other types affected by this change default to all states
    + QueryRef<TData, TVariables>
    + QueryRef<TData, TVariables, 'complete' | 'streaming'>
    
    - QueryRef<TData | undefined, TVariables>
    + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'empty'>
    
    - QueryRef<DeepPartial<TData>, TVariables>
    + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial'>
    
    - QueryRef<DeepPartial<TData> | undefined, TVariables>
    + QueryRef<TData, TVariables, 'complete' | 'streaming' | 'partial' | 'empty'>

    The following types are affected. Provide the allowed dataState values to the TDataState generic:

    • ApolloQueryResult
    • QueryRef
    • PreloadedQueryRef
    • useLazyQuery.Result
    • useQuery.Result
    • useReadQuery.Result
    • useSuspenseQuery.Result

    All *QueryRef types default to complete | streaming states while the rest of the types default to 'complete' | 'streaming' | 'partial' | 'empty' states. You shouldn't need to provide the states unless you need to either allow for partial data/empty values (*QueryRef) or a restricted set of states.

  • #12850 268cd80 Thanks @phryneas! - Introduce a versioning policy.

  • #12809 e2a0be8 Thanks @jerelmiller! - The concat, from, and split functions on ApollLink no longer support a plain request handler function. Please wrap the request handler with new ApolloLink.

    const link = new ApolloLink(/* ... */);
    
    link.concat(
    - (operation, forward) => forward(operation),
    + new ApolloLink((operation, forward) => forward(operation)),
    );
  • #12802 e2b51b3 Thanks @jerelmiller! - Disallow the mutation option for the mutate function returned from useMutation.

  • #12211 c2736db Thanks @jerelmiller! - Remove the deprecated graphql, withQuery, withMutation, withSubscription, and withApollo hoc components. Use the provided React hooks instead.

  • #12690 5812759 Thanks @phryneas! - Aliasing a field to an alias beginning with __ac_ is now forbidden - this namespace is now reserved for internal use.

  • #12559 49ace0e Thanks @jerelmiller! - When passing a variables key with the value undefined, the value will be replaced by the default value in the query, if it is provided, rather than leave it as undefined.

    // given this query
    const query = gql`
      query PaginatedQuery($limit: Int! = 10, $offset: Int) {
        list(limit: $limit, offset: $offset) {
          id
        }
      }
    `;
    
    const observable = client.query({
      query,
      variables: { limit: 5, offset: 0 },
    });
    console.log(observable.variables); // => { limit: 5, offset: 0 }
    
    observable.reobserve({ variables: { limit: undefined, offset: 10 } });
    // limit is now `10`. This would previously be `undefined`
    console.log(observable.variables); // => { limit: 10, offset: 10 }
  • #12262 10ef733 Thanks @jerelmiller! - Remove itAsync test utility.

  • #12673 cee90ab Thanks @phryneas! - Adds enhanced client awareness to the client.

    HttpLink and BatchHttpLink will now per default send information about the
    client library you are using in extensions.

    This could look like this:

    {
      "query": "query GetUser($id: ID!) { user(id: $id) { __typename id name } }",
      "variables": {
        "id": 5
      },
      "extensions": {
        "clientLibrary": {
          "name": "@apollo/client",
          "version": "4.0.0"
        }
      }
    }

    This feature can be disabled by passing enhancedClientAwareness: { transport: false } to your
    ApolloClient, HttpLink or BatchHttpLink constructor options.

  • #12742 575bf3e Thanks @jerelmiller! - The new SetContextLink flips the prevContext and operation arguments in the callback. The setContext function has remained unchanged.

    - new SetContextLink((operation, prevContext) => {
    + new SetContextLink((prevContext, operation) => {
      // ...
    })
  • #12536 e14205a Thanks @jerelmiller! - An initial loading state is now emitted from ObservableQuery when subscribing if notifyOnNetworkStatusChange is set to true.

  • #12465 a132163 Thanks @jerelmiller! - Flatten out React hook types. As a result, the base types have been removed. Prefer using the hook types instead. Removed types include:

    • BaseMutationOptions
    • BaseQueryOptions
    • BaseSubscriptionOptions
    • ObservableQueryFields
    • MutationSharedOptions
    • QueryFunctionOptions
  • #12675 8f1d974 Thanks @phryneas! - ObservableQuery no longer has a queryId property.
    ApolloClient.getObservableQueries no longer returns a Map<string, ObservableQuery>, but a Set<ObservableQuery>.

  • #12398 8cf5077 Thanks @jerelmiller! - Updates the ServerError and ServerParseError types to be proper Error subclasses. Perviously these were plain Error intances with additional properties added at runtime. All properties are retained, but instanceof checks now work correctly.

    import { ServerError, ServerParseError } from "@apollo/client";
    
    if (error instanceof ServerError) {
      // ...
    }
    
    if (error instanceof ServerParseError) {
      // ...
    }
  • #12712 bbb2b61 Thanks @jerelmiller! - cache-only queries are now excluded from client.refetchQueries in all situations. cache-only queries affected by updateCache are also excluded from refetchQueries when onQueryUpdated is not provided.

  • #12463 3868df8 Thanks @jerelmiller! - useQuery no longer returns reobserve as part of its result. It was possible to use reobserve to set new options on the underlying ObservableQuery instance which differed from the options passed to the hook. This could result in unexpected results. Instead prefer to rerender the hook with new options.

  • #12367 e6af35e Thanks @jerelmiller! - useLazyQuery no longer supports SSR environments and will now throw if the execute function is called in SSR. If you need to run a query in an SSR environment, use useQuery instead.

  • #12614 d2851e2 Thanks @jerelmiller! - Removes the resolvers option from ApolloClient. Local resolvers have instead been moved to the new LocalState instance which is assigned to the localState option in ApolloClient. To migrate, move the resolvers values into a LocalState instance and assign that instance to localState.

    new ApolloClient({
    - resolvers: { /* ... */ }
    + localState: new LocalState({
    +   resolvers: { /* ... */ }
    + }),
    });
  • #12475 3de63eb Thanks @jerelmiller! - client.mutate now returns a MutateResult instead of FetchResult. As a result, the errors property has been removed in favor of error which is set if either a network error occured or GraphQL errors are returned from the server.

    useMutation now also returns a MutateResult instead of a FetchResult.

  • #12367 e6af35e Thanks @jerelmiller! - The execute function returned from useLazyQuery now only supports the context and variables options. This means that passing options supported by the hook no longer override the hook value.

    To change options, rerender the component with new options. These options will take effect with the next query execution.

  • #12384 6aa6fd3 Thanks @jerelmiller! - ObservableQuery will no longer terminate on errors and will instead emit a next value with an error property. This ensures that ObservableQuery instances can continue to receive updates after errors are returned in requests without the need to resubscribe to the observable.

  • #12681 b181f98 Thanks @jerelmiller! - Changing most options when rerendering useQuery will no longer trigger a reobserve which may cause network fetches. Instead, the changed options will be applied to the next cache update or fetch.

    Options that now trigger a reobserve when changed between renders are:

    • query
    • variables
    • skip
    • Changing fetchPolicy to or from standby
  • #12787 8ce31fa Thanks @phryneas! - Generic arguments for Cache.ReadOptions were flipped from TVariables, TData to TData, TVariables.

  • #12837 7c49fdc Thanks @jerelmiller! - You must now opt in to use GraphQL Codegen data masking types when using Apollo Client's data masking feature. By default, Apollo Client now uses an identity type to apply to masked/unmasked types.

    If you're using GraphQL Codegen to generate masked types, opt into the GraphQL Codegen masked types using declaration merging on the TypeOverides interface.

    import { GraphQLCodegenDataMasking } from "@apollo/client/masking";
    
    declare module "@apollo/client" {
      export interface TypeOverrides
        extends GraphQLCodegenDataMasking.TypeOverrides {}
    }
  • #12824 0506f12 Thanks @jerelmiller! - Ensure the error argument for the delay and attempts functions on RetryLink are an ErrorLike.

  • #12398 8cf5077 Thanks @jerelmiller! - Removes the throwServerError utility function. Now that ServerError is an
    Error subclass, you can throw these errors directly:

    import { ServerError } from "@apollo/client";
    
    // instead of
    throwServerError(response, result, "error message");
    
    // Use
    throw new ServerError("error message", { response, result });
  • #12837 7c49fdc Thanks @jerelmiller! - The types mode for data masking has been removed. Adding a types mode to the DataMasking interface has no effect. Remove the mode key in the module where you declare the DataMasking type for the @apollo/client module.

    As a result, the Masked and MaskedDocumentNode types have also been removed since these have no effect when types are preserved.

  • #12304 86469a2 Thanks @jerelmiller! - The Cache.DiffResult<T> type is now a union type with better type safety for both complete and partial results. Checking diff.complete will now narrow the type of result depending on whether the value is true or false.

    When true, diff.result will be a non-null value equal to the T generic type. When false, diff.result now reports result as DeepPartial<T> | null indicating that fields in the result may be missing (DeepPartial<T>) or empty entirely (null).

  • #12731 0198870 Thanks @phryneas! - Ship React Compiler compiled React hooks in @apollo/client/react/compiled.

    We now ship a React-Compiler compiled version of the React hooks in
    @apollo/client/react/compiled.

    This entry point contains everything that @apollo/client/react does,
    so you can use it as a drop-in replacement in your whole application
    if you choose to use the compiled hooks.

  • #12446 ab920d2 Thanks @jerelmiller! - Removes the defaultOptions option from useQuery. Use options directly or use the global ApolloClient defaultOptions.

  • #12649 0be92ad Thanks @jerelmiller! - Remove the deprecated QueryReference type. Please use QueryRef instead.

  • #12396 00f3d0a Thanks @jerelmiller! - Remove the errors property from the results emitted from ObservableQuery or returned from client.query. Read errors from the error property instead.

  • #12367 e6af35e Thanks @jerelmiller! - The result resolved from the promise returned from the execute function in useLazyQuery is now an ApolloQueryResult type and no longer includes all the fields returned from the useLazyQuery hook tuple.

    If you need access to the additional properties such as called, refetch, etc. not included in ApolloQueryResult, read them from the hook instead.

  • #12531 7784b46 Thanks @jerelmiller! - Mocked responses passed to MockLink now accept a callback for the request.variables option. This is used to determine if the mock should be matched for a set of request variables. With this change, the variableMatcher option has been removed in favor of passing a callback to variables. Update by moving the callback function from variableMatcher to request.variables.

    new MockLink([
      {
        request: {
          query,
    +     variables: (requestVariables) => true
        },
    -   variableMatcher: (requestVariables) => true
      }
    ]);
  • #12793 24e98a1 Thanks @phryneas! - ApolloConsumer has been removed - please use useApolloClient instead.

  • #12714 0e39469 Thanks @phryneas! - Rework option handling for fetchMore.

    • Previously, if the query option was specified, no options would be inherited
      from the underlying ObservableQuery.
      Now, even if query is specified, all unspecified options except for variables will be inherited from the underlying ObservableQuery.
    • If query is not specified, variables will still be shallowly merged with the variables of the underlying ObservableQuery. If a query option is specified, the variables passed to fetchMore are used instead.
    • errorPolicy of fetchMore will now always default to "none" instead of inherited from the ObservableQuery options. This can prevent accidental cache writes of partial data for a paginated query. To opt into receive partial data that may be written to the cache, pass an errorPolicy to fetchMore to override the default.
  • #12614 d2851e2 Thanks @jerelmiller! - Remove local resolvers APIs from ApolloClient in favor of localState. Methods removed are:

    • addResolvers
    • getResolvers
    • setResolvers
    • setLocalStateFragmentMatcher
  • #12576 a92ff78 Thanks @jerelmiller! - ApolloLink.execute now requires a third argument which provides the client that initiated the request to the link chain. If you use execute directly, add a third argument with a client property:

    ApolloLink.execute(link, operation, { client });
    
    // or if you import the `execute` function directly:
    execute(link, operation, { client });
  • #12526 391af1d Thanks @phryneas! - The @apollo/client and @apollo/client/core entry points are now equal.
    In the next major, the @apollo/client/core entry point will be removed.
    Please change imports over from @apollo/client/core to @apollo/client.

  • #12700 8e96e08 Thanks @phryneas! - Added a new Streaming type that will mark data in results while dataState
    is "streaming".

    Streaming<TData> defaults to TData, but can be overwritten in userland to
    integrate with different codegen dialects.

    You can override this type globally - this example shows how to override it
    with DeepPartial<TData>:

    import { HKT, DeepPartial } from "@apollo/client/utilities";
    
    type StreamingOverride<TData> = DeepPartial<TData>;
    
    interface StreamingOverrideHKT extends HKT {
      return: StreamingOverride<this["arg1"]>;
    }
    
    declare module "@apollo/client" {
      export interface TypeOverrides {
        Streaming: StreamingOverrideHKT;
      }
    }
  • #12367 e6af35e Thanks @jerelmiller! - useLazyQuery will no longer rerender with the loading state when calling the execute function the first time unless the notifyOnNetworkStatusChange option is set to true (which is the new default).

    If you prefer the behavior from 3.x, rerender the component with
    notifyOnNetworkStatusChange set to false after the execute function is
    called the first time.

    function MyComponent() {
      const [notifyOnNetworkStatusChange, setNotifyOnNetworkStatusChange] =
        useState(true);
      const [execute] = useLazyQuery(query, { notifyOnNetworkStatusChange });
    
      async function runExecute() {
        await execute();
    
        // Set to false after the initial fetch to stop receiving notifications
        // about changes to the loading states.
        setNotifyOnNetworkStatusChange(false);
      }
    
      // ...
    }
  • #12475 3de63eb Thanks @jerelmiller! - Mutations no longer report errors if the GraphQL result from the server contains an empty array of errors.

  • #12254 0028ac0 Thanks @jerelmiller! - Changes the default Accept header to application/graphql-response+json.

  • #12633 9bfb51f Thanks @phryneas! - If the execute function of useLazyQuery is executed, previously started queries
    from the same useLazyQuery usage will be rejected with an AbortError unless
    .retain() is called on the promise returned by previous execute calls.

    Please keep in mind that useLazyQuery is primarily meant as a means to synchronize
    your component to the status of a query and that it's purpose it not to make a
    series of network calls.
    If you plan on making a series of network calls without the need to synchronize
    the result with your component, consider using ApolloClient.query instead.

  • #12513 9c3207c Thanks @phryneas! - Removed the @apollo/client/react/parser entry point. There is no replacement.

  • #12430 2ff66d0 Thanks @jerelmiller! - ObservableQuery.setVariables will now resolve with the last emitted result instead of undefined when either the variables match the current variables or there are no subscribers to the query.

  • #12685 3b74800 Thanks @jerelmiller! - Remove the check and warning for cache.fragmentMatches when applying data masking. cache.fragmentMatches is a required API and data masking may crash when cache.fragmentMatches does not exist.

  • #12385 cad5117 Thanks @phryneas! - Apollo Client now defaults to production mode, not development mode, if the
    environment cannot be determined.

    In modern bundlers, this should automatically be handled by the bundler loading
    the bundler with the development export condition.

    If neither the production nor the development export condition are
    used by the bundler/runtime, Apollo Client will fall back to globalThis.__DEV__
    to determine if it should run in production or development mode.

    Unlike Apollo Client 3 though, if globalThis.__DEV__ is not set to true,
    Apollo Client will now default to production, not to development, behaviour.

    This switch to explicilty requiring true also resolves a situation where
    an HTML element with id="__DEV__" would create a global __DEV__ variable
    with a referent to the DOM element, which in the past was picked up as "truthy" and
    would have triggered development mode.

  • #12644 fe2f005 Thanks @jerelmiller! - Change the default Accept header to application/graphql-response+json,application/json;q=0.9.

  • #12476 6afff60 Thanks @jerelmiller! - Unify error behavior on subscriptions for GraphQL errors and network errors by ensuring network errors are subject to the errorPolicy. Network errors that terminate the connection will now be emitted on the error property passed to the next callback followed by a call to the complete callback.

  • #12499 ce35ea2 Thanks @phryneas! - Enable React compiler for hooks in ESM builds.

  • #12367 e6af35e Thanks @jerelmiller! - The reobserve option is no longer available in the result returned from useLazyQuery. This was considered an internal API and should not be used directly.

  • #12333 3e4beaa Thanks @jerelmiller! - Fix type of data property on ApolloQueryResult. Previously this field was non-optional, non-null TData, however at runtime this value could be set to undefined. This field is now reported as TData | undefined.

    This will affect you in a handful of places:

    • The data property emitted from the result passed to the next callback from client.watchQuery
    • Fetch-based APIs that return an ApolloQueryResult type such as observableQuery.refetch, observableQuery.fetchMore, etc.
  • #12644 fe2f005 Thanks @jerelmiller! - HttpLink and BatchHttpLink no longer emit a next notification with the JSON-parsed response body when a well-formed GraphQL response is returned and a ServerError is thrown.

  • #12742 575bf3e Thanks @jerelmiller! - The operation argument to the callback passed to SetContextLink is now of type SetContextLink.SetContextOperation which is an Operation without the getContext or setContext functions. Previously the type of operation was GraphQLRequest which had access to a context property. The context property was always undefined and could result in bugs when using it instead of the prevContext argument.

    This change means the operation argument now contains an accessible client property.

  • #12639 1bdf489 Thanks @jerelmiller! - Move internal testing utilities in @apollo/client/testing to @apollo/client/testing/internal and remove deprecated testing utilities. Some of the testing utilities exported from the @apollo/client/testing endpoint were not considered stable. As a result of this change, testing utilities or types exported from @apollo/client/testing are now considered stable and will not undergo breaking changes.

    The following APIs were removed. To migrate, update usages of the following APIs as such:

    createMockClient

    - const client = createMockClient(data, query, variables);
    + const client = new ApolloClient({
    +   cache: new InMemoryCache(),
    +   link: new MockLink([
    +     {
    +       request: { query, variables },
    +       result: { data },
    +     }
    +   ]),
    + });

    mockObservableLink

    - const link = mockObservableLink();
    + const link = new MockSubscriptionLink();

    mockSingleLink

    - const link = mockSingleLink({
    -   request: { query, variables },
    -   result: { data },
    - });
    + const link = new MockLink([
    +   {
    +     request: { query, variables },
    +     result: { data },
    +   }
    + ]);
  • #12614 d2851e2 Thanks @jerelmiller! - Third-party caches must now implement the fragmentMatches API. Additionally fragmentMatches must be able to handle both InlineFragmentNode and FragmentDefinitionNode nodes.

    class MyCache extends ApolloCache {
      // This is now required
      public fragmentMatches(
        fragment: InlineFragmentNode | FragmentDefinitionNode,
        typename: string
      ): boolean {
        return; // ... logic to determine if typename matches fragment
      }
    }
  • #12367 e6af35e Thanks @jerelmiller! - The promise returned when calling the execute function from useLazyQuery will now reject when using an errorPolicy of none when GraphQL errors are returned from the result.

  • #12684 e697431 Thanks @jerelmiller! - Remove context from useLazyQuery hook options. If used, context must now be provided to the execute function. context will reset to {} if not provided as an option to execute.

  • #12704 45dba43 Thanks @jerelmiller! - The ErrorResponse object passed to the disable and retry callback options provided to createPersistedQueryLink no longer provides separate graphQLErrors and networkError properties and instead have been combined to a single error property of type ErrorLike.

    // The following also applies to the `retry` function since it has the same signature
    createPersistedQueryLink({
    - disable: ({ graphQLErrors, networkError }) => {
    + disable: ({ error }) => {
    -   if (graphQLErrors) {
    +   if (CombinedGraphQLErrors.is(error)) {
          // ... handle GraphQL errors
        }
    
    -   if (networkError) {
    +   if (error) {
          // ... handle link errors
        }
    
        // optionally check for a specific kind of error
    -   if (networkError) {
    +   if (ServerError.is(error)) {
          // ... handle a server error
        }
    });

    The response property has also been renamed to result.

    createPersistedQueryLink({
    -  disable: ({ response }) => {
    +  disable: ({ result }) => {
          // ... handle GraphQL errors
        }
      }
    });
  • #12823 19e315e Thanks @jerelmiller! - Move all 1st party link types into a namespace.

  • #12223 69c1cb6 Thanks @jerelmiller! - Remove subscribeAndCount testing utility from @apollo/client/testing.

  • #12300 4d581e4 Thanks @jerelmiller! - Moves all React-related exports to the @apollo/client/react entrypoint and out of the main @apollo/client entrypoint. This prevents the need to install React in order to use the core client.

    The following is a list of exports available in @apollo/client that should now import from @apollo/client/react.

    • ApolloConsumer
    • ApolloProvider
    • createQueryPreloader
    • getApolloContext
    • skipToken
    • useApolloClient
    • useBackgroundQuery
    • useFragment
    • useLazyQuery
    • useLoadableQuery
    • useMutation
    • useQuery
    • useQueryRefHandlers
    • useReactiveVar
    • useReadQuery
    • useSubscription
    • useSuspenseQuery

    The following is a list of exports available in @apollo/client/testing that should now import from @apollo/client/testing/react:

    • MockedProvider
  • #12525 8785186 Thanks @jerelmiller! - Throw an error when a client-only query is used in a mocked response passed to MockLink.

  • #12588 eed825a Thanks @jerelmiller! - Remove TContext generic argument from all types that use it. TContext is replaced with DefaultContext which can be modified using declaration merging.

  • #12647 a70fac6 Thanks @phryneas! - ObservableQuerys will now only be registered with the ApolloClient while they
    have subscribers.

    That means that ApolloClient.getObservableQueries and ApolloClient.refetchQueries
    will only be able to return/refetch queries that have at least one subscriber.

    This changes the previous meaning of active and inactive queries:

    • inactive queries are queries with a subscriber that are skipped from a
      React hook or have a fetchPolicy of standby
    • active queries are queries with at least one subscriber that are not skipped or in standby.

    ObservableQuerys without subscribers but with an active ongoing network request
    (e.g. caused by calling reobserve) will be handled as if they had a subscriber
    for the duration of the query.

  • #12809 e2a0be8 Thanks @jerelmiller! - transformOperation and validateOperation have been removed and are no longer exported from @apollo/client/link/utils. These utilities have been merged into the implementation of createOperation. As a result, createOperation now returns a well-formed Operation object. Previously createOperation relied on an external call to transformOperation to provide a well-formed Operation type. If you use createOperation directly, remove the calls to transformOperation and validateOperation and pass the request directly.

  • #12428 abed922 Thanks @jerelmiller! - Removes the urql multipart subscriptions utilities. Use the native multipart subscriptions support in urql instead.

  • #12590 a005e82 Thanks @jerelmiller! - Drop graphql v15 as a valid peer dependency.

  • #12678 91a876b Thanks @jerelmiller! - queryRefs created by preloadQuery no longer have a .toPromise() function. Instead preloadQuery now has a toPromise function that accepts a queryRef and will resolve when the underlying promise has been resolved.

    const queryRef = preloadQuery(query, options);
    
    - await queryRef.toPromise();
    + await preloadQuery.toPromise(queryRef);
  • #12556 c3fceda Thanks @phryneas! - Reworked the logic for then a loading state is triggered. If the link chain responds synchronously, a loading state will be omitted, otherwise it will be triggered.
    If local resolvers are used, the time window for "sync vs async" starts as soon as @exports variables are resolved.

  • #12637 d2a60d4 Thanks @phryneas! - useQuery: only advance previousData if data actually changed

  • #12478 5ea6a45 Thanks @jerelmiller! - Remove deprecated onSubscriptionData and onSubscriptionComplete callbacks from useSubscription. Use onData and onComplete instead.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Switch to RxJS as the observable implementation. rxjs is now a peer dependency of Apollo Client which means you will now need to install rxjs in addition to @apollo/client.

    This change is mostly transparent, however transforming values on observables, common in link implementations, differs in RxJS vs zen-observable. For example, you could modify values in the link chain emitted from a downstream link by using the .map function. In RxJS, this is done with the .pipe function and passing a map operator instead.

    import { map } from "rxjs";
    
    const link new ApolloLink((operation, forward) => {
      return forward(operation).pipe(
        map((result) => performTransform(result))
      );
    });

    For a full list of operators and comprehensive documentation on the capabilities of RxJS, check out the documentation.

  • #12442 c5ead08 Thanks @jerelmiller! - Remove the deprecated canonizeResults option. It was prone to memory leaks. As such, some results that were referentially equal when canonizeResults option was set to true no longer retain the same object identity.

  • #12809 e2a0be8 Thanks @jerelmiller! - The request handler provided to ApolloLink must now return an Observable. null is no longer supported as a valid return value. If you rely on null so that ApolloLink provides an empty observable, use the EMPTY observable from RxJS instead:

    import { ApolloLink } from "@apollo/client";
    + import { EMPTY } from "rxjs";
    
    const link = new ApolloLink((operation, forward) => {
    - return null;
    + return EMPTY;
    });

    If you have a custom link that overrides the request method, remove null from the return signature:

    class MyCustomLink extends ApolloLink {
      request(
        operation: ApolloLink.Operation,
        forward: ApolloLink.ForwardFunction,
    - ): Observable<ApolloLink.Result> | null {
    + ): Observable<ApolloLink.Result> {
        // implementation
      }
    }
  • #12329 61febe4 Thanks @phryneas! - Rework package publish format (#12329, #12382)

    We have reworked the way Apollo Client is packaged.

    • shipping ESM and CJS
    • fixing up source maps
    • the build targets a modern runtime environment (browserslist query: "since 2023, node >= 20, not dead")
    • removed the "proxy directory" package.json files, e.g. cache/core/package.json and react/package.json. While these helped with older build tools, modern build tooling uses the exports field in the root package.json instead and the presence of these files can confuse modern build tooling. If your build tooling still relies on those, please update your imports to import from e.g. @apollo/client/cache/core/index.js instead of @apollo/client/cache/core - but generally, this should not be necessary.
    • added an exports field to package.json to expose entry points
    • instead of globalThis.__DEV__, Apollo Client now primarily relies on the development and production exports conditions. It falls back to globalThis.__DEV__ if the bundler doesn't know these, though.
  • #12566 ce4b488 Thanks @jerelmiller! - Don't broadcastQueries when a query is torn down.

  • #12397 2545a54 Thanks @jerelmiller! - Remove ObservableQuery.resetQueryStoreErrors method. This method reset some internal state that was not consumed elsewhere in the client and resulted in a no-op.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Remove fromPromise utility function. Use from instead.

  • #12388 0d825be Thanks @jerelmiller! - Require environments that support WeakMap, WeakSet and symbols. Apollo Client would fallback to Map and Set if the weak versions were not available. This has been removed and expects that these features are available in the source environment.

    If you are running in an environment without WeakMap, WeakSet or symbols, you will need to find appropriate polyfills.

  • #12367 e6af35e Thanks @jerelmiller! - useLazyQuery no longer supports calling the execute function in render and will now throw. If you need to execute the query immediately, use useQuery instead or move the call to a useEffect.

  • #12631 b147cac Thanks @phryneas! - ObservableQuery will now return a loading: false state for fetchPolicy standby, even before subscription

  • #12639 1bdf489 Thanks @jerelmiller! - Remove the @apollo/client/testing/core entrypoint in favor of @apollo/client/testing.

  • #12591 a7e7383 Thanks @jerelmiller! - Rename the @apollo/client/link/core entrypoint to @apollo/client/link.

  • #12367 e6af35e Thanks @jerelmiller! - The defaultOptions and initialFetchPolicy options are no longer supported with useLazyQuery.

    If you use defaultOptions, pass those options directly to the hook instead. If you use initialFetchPolicy, use fetchPolicy instead.

  • #12823 19e315e Thanks @jerelmiller! - The OperationBatcher class is no longer exported from @apollo/client/link/batch. It is an implementation detail of BatchLink and should not be relied on directly.

  • #12809 e2a0be8 Thanks @jerelmiller! - createOperation no longer accepts context as the first argument. Instead make sure context is set as the context property on the request passed to createOperation.

    createOperation(
    - startingContext,
    - { query },
    + { query, context: startingContext },
      { client }
    );
  • #12556 c3fceda Thanks @phryneas! - Dropped the saveAsLastResult argument from ObservableQuery.getCurrentResult

  • #12367 e6af35e Thanks @jerelmiller! - useLazyQuery no longer supports variables in the hook options and therefore no longer performs variable merging. The execute function must now be called with variables instead.

    function MyComponent() {
      const [execute] = useLazyQuery(query);
    
      function runExecute() {
        execute({ variables: { ... }});
      }
    }

    This change means the execute function returned from useLazyQuery is more type-safe. The execute function will require you to pass a variables option if the query type includes required variables.

  • #12614 d2851e2 Thanks @jerelmiller! - The resolver function's context argument (the 3rd argument) has changed to provide additional information without the possibility of name clashes. Previously the context argument would spread request context and override the client and cache properties to give access to both inside of a resolver. The context argument takes now takes the following shape:

    {
      // the request context. By default `TContextValue` is of type `DefaultContext`,
      // but can be changed if a `context` function is provided.
      requestContext: TContextValue,
      // The client instance making the request
      client: ApolloClient,
      // Whether the resolver is run as a result of gathering exported variables
      // or resolving the value as part of the result
      phase: "exports" | "resolve"
    }

    To migrate, pull any request context from requestContext and the cache from the client property:

    new LocalState({
      resolvers: {
        Query: {
    -     myResolver: (parent, args, { someValue, cache }) => {
    +     myResolver: (parent, args, { requestContext, client }) => {
    +       const someValue = requestContext.someValue;
    +       const cache = client.cache;
          }
        }
      }
    });
  • #12712 bbb2b61 Thanks @jerelmiller! - cache-only queries no longer poll when a pollInterval is set. Instead a warning is now emitted that polling has no effect. If the fetchPolicy is changed to cache-only after polling is already active, polling is stopped.

  • #12704 45dba43 Thanks @jerelmiller! - The response property in onError link has been renamed to result.

    - onError(({ response }) => {
    + onError(({ result }) => {
        // ...
    });
  • #12614 d2851e2 Thanks @jerelmiller! - Apollo Client no longer ships with support for @client fields out-of-the-box and now must be opt-in. To opt in to use @client fields, pass an instantiated LocalState instance to the localState option. If a query contains @client and local state hasn't been configured, an error will be thrown.

    import { LocalState } from "@apollo/client/local-state";
    
    new ApolloClient({
      localState: new LocalState(),
    });
  • #12532 ae0dcad Thanks @jerelmiller! - Default the delay for all mocked responses passed to MockLink using realisticDelay. This ensures your test handles loading states by default and is not reliant on a specific timing.

    If you would like to restore the old behavior, use a global default delay of 0.

    MockLink.defaultOptions = {
      delay: 0,
    };
  • #12442 c5ead08 Thanks @jerelmiller! - Remove resetResultIdentities option from InMemoryCache.gc(). This affected object canonization which has been removed.

  • #12304 86469a2 Thanks @jerelmiller! - ### Changes for users of InMemoryCache

    cache.diff now returns null instead of an empty object ({}) when returnPartialData is true and the result is empty.

    If you use cache.diff directly with returnPartialData: true, you will need to check for null before accessing any other fields on the result property. A non-null value indicates that at least one field was present in the cache for the given query document.

    Changes for third-party cache implementations

    The client now expects cache.diff to return null instead of an empty object when there is no data that can be fulfilled from the cache and returnPartialData is true. If your cache implementation returns an empty object, please update this to return null.

  • #12433 b86e50b Thanks @phryneas! - Remove workarounds for streaming with non-WhatWG response bodies to reduce bundle size.

    This removes support for fetch implementations that return Node Streams, Async Iterators or Blob instances as Response.body.

    In the WhatWG Fetch specification, Response.body is specified as a WhatWG ReadableStream.

    At this point in time, this is natively supported in browsers, node and React Native (via react-native-fetch-api, see our setup instructions for React Native).

    If you are using an older fetch polyfill that deviates from the spec, this might not be compatible - for example, node-fetch returns a node Readable instead of a ReadableStream.
    In those cases, please switch to a compatible alternative such as the node-native fetch, or undici.

  • #12484 9a8b9ce Thanks @jerelmiller! - Remove loading, networkStatus, and partial properties on all promise-based query APIs. These properties were mostly static and were unnecessary since promise resolution guaranteed that the query was not longer loading.

    This affects the following APIs:

    • client.query
    • client.refetchQueries
    • client.reFetchObservableQueries
    • client.resetStore
    • observableQuery.fetchMore
    • observableQuery.refetch
    • observableQuery.reobserve
    • observableQuery.setVariables
    • The useLazyQuery execute function
  • #12512 e809b71 Thanks @jerelmiller! - notifyOnNetworkStatusChange now defaults to true. This means that loading states will be emitted (core API) or rendered (React) by default when calling refetch, fetchMore, etc. To maintain the old behavior, set notifyOnNetworkStatusChange to false in defaultOptions.

    new ApolloClient({
      defaultOptions: {
        watchQuery: {
          // Use the v3 default
          notifyOnNetworkStatusChange: false,
        },
      },
    });
  • #12614 d2851e2 Thanks @jerelmiller! - Remove the fragmentMatcher option from ApolloClient. Custom fragment matchers used with local state are no longer supported. Fragment matching is now performed by the configured cache via the cache.fragmentMatches API.

  • #12430 2ff66d0 Thanks @jerelmiller! - Removes ObservableQuery.result() method. If you use this method and need similar functionality, use the firstValueFrom helper in RxJS.

    import { firstValueFrom, from } from "rxjs";
    
    // The `from` is necessary to turn `observableQuery` into an RxJS observable
    const result = await firstValueFrom(from(observableQuery));
  • #12359 ebb4d96 Thanks @jerelmiller! - Remove the onCompleted and onError callbacks from useQuery and useLazyQuery.

    See #12352 for more context on this change.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Subscriptions are no longer eagerly started after calling client.subscribe. To kick off the subscription, you will now need to subscribe to the returned observable.

    // Subscriptions are no longer started when calling subscribe on its own.
    const subscriptionObservable = client.subscribe(...);
    
    // Instead, subscribe to the returned observable to kick off the subscription.
    subscriptionObservable.subscribe({
      next: (value) => console.log(value)
    });
  • #12476 6afff60 Thanks @jerelmiller! - GraphQL errors or network errors emitted while using an errorPolicy of ignore in subscriptions will no longer emit a result if there is no data emitted along with the error.

  • #12367 e6af35e Thanks @jerelmiller! - useLazyQuery will now only execute the query when the execute function is called. Previously useLazyQuery would behave like useQuery after the first call to the execute function which means changes to options might perform network requests.

    You can now safely rerender useLazyQuery with new options which will take effect the next time you manually trigger the query.

  • #12809 e2a0be8 Thanks @jerelmiller! - Remove the TVariables generic argument on the GraphQLRequest type.

  • #12464 0595f39 Thanks @jerelmiller! - Remove the called property from useQuery.

  • #12384 6aa6fd3 Thanks @jerelmiller! - Remove toPromise utility function. Use firstValueFrom instead.

  • #12476 6afff60 Thanks @jerelmiller! - Subscriptions no longer emit errors in the error callback and instead provide errors on the error property on the result passed to the next callback. As a result, errors will no longer automatically terminate the connection allowing additional results to be emitted when the connection stays open.

    When an error terminates the downstream connection, a next event will be emitted with an error property followed by a complete event instead.

  • #12841 65b503f Thanks @jerelmiller! - Remove the DataMasking interface exported from @apollo/client and @apollo/client/masking.

  • #12809 e2a0be8 Thanks @jerelmiller! - The context object returned from operation.getContext() is now frozen to prevent mutable changes to the object which could result in subtle bugs. This applies to the previousContext object passed to the operation.setContext() callback as well.

  • #12715 0be0b3f Thanks @phryneas! - All links are now available as classes. The old creator functions have been deprecated.

    Please migrate these function calls to class creations:

    import {
    - setContext
    + SetContextLink
    } from "@apollo/client/link/context"
    
    -const link = setContext(...)
    +const link = new SetContextLink(...)
    import {
    - createHttpLink
    + HttpLink
    } from "@apollo/client/link/http"
    
    -const link = createHttpLink(...)
    +const link = new HttpLink(...)
    import {
    - createPersistedQueryLink
    + PersistedQueryLink
    } from "@apollo/client/link/persisted-queries"
    
    -const link = createPersistedQueryLink(...)
    +const link = new PersistedQueryLink(...)
    import {
    - removeTypenameFromVariables
    + RemoveTypenameFromVariablesLink
    } from "@apollo/client/link/remove-typename"
    
    -const link = removeTypenameFromVariables(...)
    +const link = new RemoveTypenameFromVariablesLink(...)
  • #12530 2973e2a Thanks @jerelmiller! - Remove newData option for mocked responses passed to MockLink or the mocks option on MockedProvider. This option was undocumented and was nearly identical to using the result option as a callback.

    To replicate the old behavior of newData, use result as a callback and add the maxUsageCount option with a value set to Number.POSITIVE_INFINITY.

    with MockLink

    new MockLink([
      {
        request: { query, variables },
    -   newData: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }),
    +   result: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }),
    +   maxUsageCount: Number.POSITIVE_INFINITY,
      }
    ])

    with MockedProvider

    <MockedProvider
      mocks={[
        {
          request: { query, variables },
    -     newData: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }),
    +     result: (variables) => ({ data: { greeting: "Hello " + variables.greeting } }),
    +     maxUsageCount: Number.POSITIVE_INFINITY,
        }
      ]}
    />
  • #12556 c3fceda Thanks @phryneas! - A call to ObservableQuery.setVariables with different variables or a ObservableQuery.refetch call will always now guarantee that a value will be emitted from the observable, even if it is deep equal to the previous value.

  • #12647 a70fac6 Thanks @phryneas! - ApolloClient.stop() now cleans up more agressively to prevent memory leaks:

    • It will now unsubscribe all active ObservableQuery instances by emitting a completed event.
    • It will now reject all currently running queries with "QueryManager stopped while query was in flight".
    • It will remove all queryRefs from the suspense cache.
  • #12809 e2a0be8 Thanks @jerelmiller! - The forward function passed to the request handler is now always provided to request and no longer optional. If you create custom links by subclassing ApolloLink, the forward function no longer needs to be optional:

    class CustomLink extends ApolloLink {
      request(
        operation: ApolloLink.Operation,
        // This no longer needs to be typed as optional
        forward: ApolloLink.ForwardFunction
      ) {
        // ...
      }
    }

    As a result of this change, ApolloLink no longer detects terminating links by checking function arity on the request handler. This means using methods such as concat on a terminating link no longer emit a warning. On the flip side, if the terminating link calls the forward function, a warning is emitted and an observable that immediately completes is returned which will result in an error from Apollo Client.

  • #12589 15f5a1c Thanks @jerelmiller! - Require the link option when instantiating ApolloClient. This removes the uri, credentials and headers options from ApolloClient in favor of passing an instantiated HttpLink directly. To migrate:

    If using uri, credentials, or headers options

    new ApolloClient({
      // ...
    - uri,
    - credentials,
    - headers,
    + link: new HttpLink({ uri, credentials, headers }),
    // or if you prefer the function call approach:
    + link: createHttpLink({ uri, credentials, headers }),
    });

    If creating a client without the link option

    new ApolloClient({
      // ...
    + link: ApolloLink.empty()
    });
  • #12304 86469a2 Thanks @jerelmiller! - ### Changes for users of InMemoryCache

    cache.diff no longer throws when returnPartialData is set to false without a complete result. Instead, cache.diff will return null when it is unable to read a full cache result.

    If you use cache.diff directly with returnPartialData: false, remove the try/catch block and replace with a check for null.

    Changes for third-party cache implementations

    The client now expects cache.diff to return null instead of throwing when the cache returns an incomplete result and returnPartialData is false. The internal try/catch blocks have been removed around cache.diff. If your cache implementation throws for incomplete results, please update this to return null.

  • #12451 77e1b13 Thanks @jerelmiller! - Default the TData generic type to unknown in all APIs that use a TData generic argument such as useQuery, client.query, etc.

  • #12562 90bf0e6 Thanks @jerelmiller! - client.query no longer supports a fetchPolicy of standby. standby does not fetch and did not return data. standby is meant for watched queries where fetching should be on hold.

  • #12211 c2736db Thanks @jerelmiller! - Remove the deprecated Query, Mutation, and Subscription components. Use the provided React hooks instead.

Minor changes

For a comprehensive look at all the minor changes, see the changelog.

Patch changes

For a comprehensive look at all the patch changes, see the changelog.

Don't miss a new apollo-client release

NewReleases is sending notifications on new releases.