docs/source/migrating/apollo-client-4-migration.mdx
This article walks you through migrating your application to Apollo Client 4.0 from a previous installation of Apollo Client 3.14.
<Note>If you are migrating from older versions of Apollo Client 3, we generally recommend updating to Apollo Client 3.14 first. Apollo Client 3.14 introduces many deprecations and warnings that will help you prepare your application for a smooth migration to Apollo Client 4.0.
</Note>@apollo/client/reactexports field in package.jsonrxjs instead of zen-observableFor a full list of changes, see the changelog.
Apollo Client 4.0 is a major-version release that includes breaking changes. If you are updating an existing application to use Apollo Client 4.0, see the changelog for details about these changes.
</Caution>Install Apollo Client 4 along with its peer dependencies with the following command:
npm install @apollo/client@latest graphql rxjs
rxjs is a new peer dependency with Apollo Client 4.0.
We recommend following this order to help make your migration as smooth as possible.
Run the codemod first to handle import changes automatically.
You can use the TypeScript-specific codemod or run specific modifications if you prefer a gradual approach.
If you prefer a manual approach, you can still update your imports manually.
The Apollo Client initialization has changed. Review the new initialization options and update accordingly.
If you use @defer, you need to enable incremental delivery.
If you use data masking, you need to configure the data masking types.
These changes may require code refactoring depending on your usage.
useLazyQuery, useMutation, useQuery updates@client directivesTo ease the migration process, we have provided a codemod that automatically updates your codebase.
<Note>The codemod doesn't update your code with every breaking change. We recommend reading through the full migration guide for any additional changes that need refactoring.
</Note>This codemod runs modifications in the following order:
<ol><li>legacyEntrypoints step:
Updates CommonJS import statements using the .cjs extension to their associated entry point
import { ApolloClient } from "@apollo/client/main.cjs";// [!code --]
import { ApolloClient } from "@apollo/client";// [!code ++]
Updates ESM import statements using the .js extension to their associated entry point
import { ApolloClient } from "@apollo/client/index.js";// [!code --]
import { ApolloClient } from "@apollo/client";// [!code ++]
imports step:
Updates imports that have moved around
import { useQuery } from "@apollo/client";// [!code --]
import { useQuery } from "@apollo/client/react";// [!code ++]
Updates type references that have moved into namespaces.
import { FetchResult } from "@apollo/client";// [!code --]
import { ApolloLink } from "@apollo/client";// [!code ++]
// ...
fn<FetchResult>();// [!code --]
fn<ApolloLink.Result>();// [!code ++]
links step:
Updates the usage of Apollo-provided links to their associated class implementation.
import { createHttpLink } from "@apollo/client";// [!code --]
import { HttpLink } from "@apollo/client/link/http";// [!code ++]
// ...
const link = createHttpLink({ uri: "https://example.com/graphql" });// [!code --]
const link = new HttpLink({ uri: "https://example.com/graphql" });// [!code ++]
If you use the setContext link, you will need to manually migrate to the SetContextLink class because the order of arguments has changed. This is not automatically performed by the codemod.
Updates the usage of from, split and concat functions from @apollo/client/link to use the static methods on the ApolloLink class. For example:
import { from } from "@apollo/client";// [!code --]
import { ApolloLink } from "@apollo/client";// [!code ++]
// ...
const link = from([a, b, c]);// [!code --]
const link = ApolloLink.from([a, b, c]);// [!code ++]
removals step:
Updates exports removed from Apollo Client to a special @apollo/client/v4-migration entrypoint. This is a type-only entrypoint that contains doc blocks with migration instructions for each removed item.
import { Concast } from "@apollo/client";// [!code --]
import { Concast } from "@apollo/client/v4-migration";// [!code ++]
Any runtime values exported from @apollo/client/v4-migration will throw an error at runtime since their implementations do not exist.
clientSetup step:
Moves uri, headers and credentials to the link option and creates a new HttpLink instance
import { HttpLink } from "@apollo/client"; // [!code ++]
new ApolloClient({
uri: "/graphql", // [!code --:5]
credentials: "include",
headers: {
"x-custom-header"
},
link: new HttpLink({ // [!code ++:6]
uri: "/graphql",
credentials: "include",
headers: {
"x-custom-header"
},
})
})
Moves name and version into a clientAwareness option
new ApolloClient({
name: "my-client",// [!code --:2]
version: "1.0.0",
clientAwareness: {// [!code ++:4]
name: "my-client",
version: "1.0.0",
},
});
Adds a localState option with a new LocalState instance, moves the resolvers to LocalState, and removes the typeDefs and fragmentMatcher options
import { LocalState } from "@apollo/client/local-state";// [!code ++]
new ApolloClient({
typeDefs,// [!code --]
fragmentMatcher: () => true,// [!code --]
resolvers: {// [!code --:3]
/* ... */
},
localState: new LocalState({// [!code ++:5]
resolvers: {
/* ... */
},
}),
});
Updates the connectToDevTools option to devtools.enabled
new ApolloClient({
connectToDevTools: true,// [!code --]
devtools: {// [!code ++:3]
enabled: true,
},
});
Renames disableNetworkFetches to prioritizeCacheValues
new ApolloClient({
disableNetworkFetches: true,// [!code --]
prioritizeCacheValues: true,// [!code ++]
});
Adds a template for global type augmentation to re-enable data masking types when the dataMasking option is set to true
new ApolloClient({
// [!code ++:8]
/*
Inserted by Apollo Client 3->4 migration codemod.
Keep this comment here if you intend to run the codemod again,
to avoid changes from being reapplied.
Delete this comment once you are done with the migration.
@apollo/client-codemod-migrate-3-to-4 applied
*/
dataMasking: true,
});
// [!code ++:15]
/*
Start: Inserted by Apollo Client 3->4 migration codemod.
Copy the contents of this block into a \`.d.ts\` file in your project
to enable data masking types.
*/
import "@apollo/client";
import { GraphQLCodegenDataMasking } from "@apollo/client/masking";
declare module "@apollo/client" {
export interface TypeOverrides
extends GraphQLCodegenDataMasking.TypeOverrides {}
}
/*
End: Inserted by Apollo Client 3->4 migration codemod.
*/
Adds the incrementalHandler option and adds a template for global type augmentation to accordingly type network responses in custom links
import { Defer20220824Handler } from "@apollo/client/incremental";// [!code ++]
new ApolloClient({
// [!code ++:7]
/*
If you are not using the \`@defer\` directive in your application,
Inserted by Apollo Client 3->4 migration codemod.
you can safely remove this option.
*/
incrementalHandler: new Defer20220824Handler(),
});
// [!code ++:14]
/*
Start: Inserted by Apollo Client 3->4 migration codemod.
Copy the contents of this block into a \`.d.ts\` file in your project to enable correct response types in your custom links.
If you do not use the \`@defer\` directive in your application, you can safely remove this block.
*/
import "@apollo/client";
import { Defer20220824Handler } from "@apollo/client/incremental";
declare module "@apollo/client" {
export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {}
}
/*
End: Inserted by Apollo Client 3->4 migration codemod.
*/
The codemod requires a Node.js version that supports loading ECMAScript modules with require. See the table in the Node.js documentation for the versions that support this feature (you might need to expand the "History" section to see the table.)
To run the codemod, use the following command:
npx @apollo/client-codemod-migrate-3-to-4 src
This command behaves similarly to jscodeshift with a preselected codemod.
This example targets the src directory with the codemod. Replace src with the file pattern applicable to your file structure if it differs.
For more details on the available options, run the command using the --help option.
npx @apollo/client-codemod-migrate-3-to-4 --help
If you have a TypeScript project, we recommend running the codemod against .ts and .tsx files separately as those have slightly overlapping syntax and might otherwise be misinterpreted by the codemod.
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
If you prefer to migrate your application more selectively instead of all at once, you can specify specific modifications using the --codemod option. For example, to run only the imports and links modifications, run the following command:
npx @apollo/client-codemod-migrate-3-to-4 --codemod imports --codemod links src
The following codemods are available:
clientSetup - Updates the options provided to the ApolloClient constructor to use their new counterpartslegacyEntrypoints - Renames import statements that target .js or .cjs imports to their associated entry point (e.g. @apollo/client/main.cjs becomes @apollo/client)imports - Updates import statements that have moved entry points and updates type references that have moved into a namespace.links - Updates provided Apollo Links to their class-based alternative (e.g. createHttpLink() becomes new HttpLink()).removals - Updates import statements that contain removed features to a special @apollo/client/v4-migration entry point.exportsThis section contains instructions for manually updating imports. If you used the Codemod to update your imports, you can safely skip this section.
</Note>Apollo Client 4 now includes an exports field in the package's package.json definition. Instead of importing .js or .cjs files directly (e.g. @apollo/client/react/index.js, @apollo/client/react/react.cjs, etc.), you now import from the entrypoint instead (e.g. @apollo/client/react).
Your bundler is aware of the different module formats and uses the exports field of the package's package.json to resolve the right format.
The following entry points have been renamed:
| Previous entry point | New entry point |
|---|---|
@apollo/client/core | @apollo/client |
@apollo/client/link/core | @apollo/client/link |
@apollo/client/react/context | @apollo/client/react |
@apollo/client/react/hooks | @apollo/client/react |
@apollo/client/testing/core | @apollo/client/testing |
The following entry points have been removed:
| Previous entry point | Reason |
|---|---|
@apollo/client/react/components | The render prop components were already deprecated in Apollo Client 3.x and have been removed in 4. |
@apollo/client/react/hoc | The higher order components were already deprecated in Apollo Client 3.x and have been removed in 4. |
@apollo/client/react/parser | This module was an implementation detail of the render prop components and HOCs. |
@apollo/client/testing/experimental | This is available as @apollo/graphql-testing-library |
@apollo/client/utilities/globals | This was an implementation detail and has been removed. Some of the exports are now available in other entry points. |
@apollo/client/utilities/subscriptions/urql | This is supported natively by urql and is no longer included. |
The following individual imports have been renamed, moved to new entry points and/or moved into namespaces:
<table> <thead> <tr><th>Previous entry point</th><th>New entry point</th></tr> <tr><td>Previous import name</td><td>New import name</td></tr> </thead> <tbody> <tr><th>`@apollo/client`</th><th>(unchanged)</th></tr> <tr><td>`ApolloClientOptions`</td><td>`ApolloClient.Options`</td></tr> <tr><td>`DefaultOptions`</td><td>`ApolloClient.DefaultOptions`</td></tr> <tr><td>`DevtoolsOptions`</td><td>`ApolloClient.DevtoolsOptions`</td></tr> <tr><td>`MutateResult`</td><td>`ApolloClient.MutateResult`</td></tr> <tr><td>`MutationOptions`</td><td>`ApolloClient.MutateOptions`</td></tr> <tr><td>`QueryOptions`</td><td>`ApolloClient.QueryOptions`</td></tr> <tr><td>`RefetchQueriesOptions`</td><td>`ApolloClient.RefetchQueriesOptions`</td></tr> <tr><td>`RefetchQueriesResult`</td><td>`ApolloClient.RefetchQueriesResult`</td></tr> <tr><td>`SubscriptionOptions`</td><td>`ApolloClient.SubscribeOptions`</td></tr> <tr><td>`WatchQueryOptions`</td><td>`ApolloClient.WatchQueryOptions`</td></tr> <tr><td>`ApolloQueryResult`</td><td>`ObservableQuery.Result`</td></tr> <tr><td>`FetchMoreOptions`</td><td>`ObservableQuery.FetchMoreOptions`</td></tr> <tr><td>`SubscribeToMoreOptions`</td><td>`ObservableQuery.SubscribeToMoreOptions`</td></tr> </tbody><tbody> <tr><th>`@apollo/client`</th><th>`@apollo/client/react`</th></tr> <tr><td>`ApolloProvider`</td><td>(unchanged)</td></tr> <tr><td>`createQueryPreloader`</td><td>(unchanged)</td></tr> <tr><td>`getApolloContext`</td><td>(unchanged)</td></tr> <tr><td>`skipToken`</td><td>(unchanged)</td></tr> <tr><td>`useApolloClient`</td><td>(unchanged)</td></tr> <tr><td>`useBackgroundQuery`</td><td>(unchanged)</td></tr> <tr><td>`useFragment`</td><td>(unchanged)</td></tr> <tr><td>`useLazyQuery`</td><td>(unchanged)</td></tr> <tr><td>`useLoadableQuery`</td><td>(unchanged)</td></tr> <tr><td>`useMutation`</td><td>(unchanged)</td></tr> <tr><td>`useQuery`</td><td>(unchanged)</td></tr> <tr><td>`useQueryRefHandlers`</td><td>(unchanged)</td></tr> <tr><td>`useReactiveVar`</td><td>(unchanged)</td></tr> <tr><td>`useReadQuery`</td><td>(unchanged)</td></tr> <tr><td>`useSubscription`</td><td>(unchanged)</td></tr> <tr><td>`useSuspenseFragment`</td><td>(unchanged)</td></tr> <tr><td>`useSuspenseQuery`</td><td>(unchanged)</td></tr> <tr><td>`ApolloContextValue`</td><td>(unchanged)</td></tr> <tr><td>`BackgroundQueryHookFetchPolicy`</td><td>`useBackgroundQuery.FetchPolicy`</td></tr> <tr><td>`BackgroundQueryHookOptions`</td><td>`useBackgroundQuery.Options`</td></tr> <tr><td>`BaseSubscriptionOptions`</td><td>`useSubscription.Options`</td></tr> <tr><td>`Context`</td><td>(unchanged)</td></tr> <tr><td>`LazyQueryExecFunction`</td><td>`useLazyQuery.ExecFunction`</td></tr> <tr><td>`LazyQueryHookExecOptions`</td><td>`useLazyQuery.ExecOptions`</td></tr> <tr><td>`LazyQueryHookOptions`</td><td>`useLazyQuery.Options`</td></tr> <tr><td>`LazyQueryResult`</td><td>`useLazyQuery.Result`</td></tr> <tr><td>`LazyQueryResultTuple`</td><td>`useLazyQuery.ResultTuple`</td></tr> <tr><td>`LoadableQueryHookFetchPolicy`</td><td>`useLoadableQuery.FetchPolicy`</td></tr> <tr><td>`LoadableQueryHookOptions`</td><td>`useLoadableQuery.Options`</td></tr> <tr><td>`LoadQueryFunction`</td><td>`useLoadableQuery.LoadQueryFunction`</td></tr> <tr><td>`MutationFunction`</td><td>(unchanged)</td></tr> <tr><td>`MutationFunctionOptions`</td><td>`useMutation.MutationFunctionOptions`</td></tr> <tr><td>`MutationHookOptions`</td><td>`useMutation.Options`</td></tr> <tr><td>`MutationResult`</td><td>`useMutation.Result`</td></tr> <tr><td>`MutationTuple`</td><td>`useMutation.ResultTuple`</td></tr> <tr><td>`NoInfer`</td><td>(unchanged)</td></tr> <tr><td>`OnDataOptions`</td><td>`useSubscription.OnDataOptions`</td></tr> <tr><td>`OnSubscriptionDataOptions`</td><td>`useSubscription.OnSubscriptionDataOptions`</td></tr> <tr><td>`PreloadedQueryRef`</td><td>(unchanged)</td></tr> <tr><td>`PreloadQueryFetchPolicy`</td><td>(unchanged)</td></tr> <tr><td>`PreloadQueryFunction`</td><td>(unchanged)</td></tr> <tr><td>`PreloadQueryOptions`</td><td>(unchanged)</td></tr> <tr><td>`QueryFunctionOptions`</td><td>`useQuery.Options`</td></tr> <tr><td>`QueryHookOptions`</td><td>`useQuery.Options`</td></tr> <tr><td>`QueryRef`</td><td>(unchanged)</td></tr> <tr><td>`QueryReference`</td><td>`QueryRef`</td></tr> <tr><td>`QueryResult`</td><td>`useQuery.Result`</td></tr> <tr><td>`QueryTuple`</td><td>`useLazyQuery.ResultTuple`</td></tr> <tr><td>`SkipToken`</td><td>(unchanged)</td></tr> <tr><td>`SubscriptionDataOptions`</td><td>(unchanged)</td></tr> <tr><td>`SubscriptionHookOptions`</td><td>`useSubscription.Options`</td></tr> <tr><td>`SubscriptionResult`</td><td>`useSubscription.Result`</td></tr> <tr><td>`SuspenseQueryHookFetchPolicy`</td><td>`useSuspenseQuery.FetchPolicy`</td></tr> <tr><td>`SuspenseQueryHookOptions`</td><td>`useSuspenseQuery.Options`</td></tr> <tr><td>`UseBackgroundQueryResult`</td><td>`useBackgroundQuery.Result`</td></tr> <tr><td>`UseFragmentOptions`</td><td>`useFragment.Options`</td></tr> <tr><td>`UseFragmentResult`</td><td>`useFragment.Result`</td></tr> <tr><td>`UseLoadableQueryResult`</td><td>`useLoadableQuery.Result`</td></tr> <tr><td>`UseQueryRefHandlersResult`</td><td>`useQueryRefHandlers.Result`</td></tr> <tr><td>`UseReadQueryResult`</td><td>`useReadQuery.Result`</td></tr> <tr><td>`UseSuspenseFragmentOptions`</td><td>`useSuspenseFragment.Options`</td></tr> <tr><td>`UseSuspenseFragmentResult`</td><td>`useSuspenseFragment.Result`</td></tr> <tr><td>`UseSuspenseQueryResult`</td><td>`useSuspenseQuery.Result`</td></tr> <tr><td>`VariablesOption`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/cache`</th><th>(unchanged)</th></tr> <tr><td>`WatchFragmentOptions`</td><td>`ApolloCache.WatchFragmentOptions`</td></tr> <tr><td>`WatchFragmentResult`</td><td>`ApolloCache.WatchFragmentResult`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link`</th><th>`@apollo/client/incremental`</th></tr> <tr><td>`ExecutionPatchIncrementalResult`</td><td>`Defer20220824Handler.SubsequentResult`</td></tr> <tr><td>`ExecutionPatchInitialResult`</td><td>`Defer20220824Handler.InitialResult`</td></tr> <tr><td>`ExecutionPatchResult`</td><td>`Defer20220824Handler.Chunk`</td></tr> <tr><td>`IncrementalPayload`</td><td>`Defer20220824Handler.IncrementalDeferPayload`</td></tr> <tr><td>`Path`</td><td>`Incremental.Path`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link`</th><th>(unchanged)</th></tr> <tr><td>`FetchResult`</td><td>`ApolloLink.Result`</td></tr> <tr><td>`GraphQLRequest`</td><td>`ApolloLink.Request`</td></tr> <tr><td>`NextLink`</td><td>`ApolloLink.ForwardFunction`</td></tr> <tr><td>`Operation`</td><td>`ApolloLink.Operation`</td></tr> <tr><td>`RequestHandler`</td><td>`ApolloLink.RequestHandler`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link`</th><th>`graphql`</th></tr> <tr><td>`SingleExecutionResult`</td><td>`FormattedExecutionResult`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/batch`</th><th>(unchanged)</th></tr> <tr><td>`BatchHandler`</td><td>`BatchLink.BatchHandler`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/context`</th><th>(unchanged)</th></tr> <tr><td>`ContextSetter`</td><td>`SetContextLink.LegacyContextSetter`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/error`</th><th>(unchanged)</th></tr> <tr><td>`ErrorHandler`</td><td>`ErrorLink.ErrorHandler`</td></tr> <tr><td>`ErrorResponse`</td><td>`ErrorLink.ErrorHandlerOptions`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/http`</th><th>`@apollo/client/errors`</th></tr> <tr><td>`ServerParseError`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/persisted-queries`</th><th>(unchanged)</th></tr> <tr><td>`ErrorResponse`</td><td>`PersistedQueryLink.DisableFunctionOptions`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/remove-typename`</th><th>(unchanged)</th></tr> <tr><td>`RemoveTypenameFromVariablesOptions`</td><td>`RemoveTypenameFromVariablesLink.Options`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/utils`</th><th>`@apollo/client/errors`</th></tr> <tr><td>`ServerError`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/link/ws`</th><th>(unchanged)</th></tr> <tr><td>`WebSocketParams`</td><td>`WebSocketLink.Configuration`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/react`</th><th>`@apollo/client`</th></tr> <tr><td>`Context`</td><td>`DefaultContext`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/react`</th><th>(unchanged)</th></tr> <tr><td>`QueryReference`</td><td>`QueryRef`</td></tr> <tr><td>`ApolloProviderProps`</td><td>`ApolloProvider.Props`</td></tr> <tr><td>`BackgroundQueryHookFetchPolicy`</td><td>`useBackgroundQuery.FetchPolicy`</td></tr> <tr><td>`BackgroundQueryHookOptions`</td><td>`useBackgroundQuery.Options`</td></tr> <tr><td>`UseBackgroundQueryResult`</td><td>`useBackgroundQuery.Result`</td></tr> <tr><td>`LazyQueryExecFunction`</td><td>`useLazyQuery.ExecFunction`</td></tr> <tr><td>`LazyQueryHookExecOptions`</td><td>`useLazyQuery.ExecOptions`</td></tr> <tr><td>`LazyQueryHookOptions`</td><td>`useLazyQuery.Options`</td></tr> <tr><td>`LazyQueryResult`</td><td>`useLazyQuery.Result`</td></tr> <tr><td>`LazyQueryResultTuple`</td><td>`useLazyQuery.ResultTuple`</td></tr> <tr><td>`QueryTuple`</td><td>`useLazyQuery.ResultTuple`</td></tr> <tr><td>`LoadableQueryFetchPolicy`</td><td>`useLoadableQuery.FetchPolicy`</td></tr> <tr><td>`LoadableQueryHookFetchPolicy`</td><td>`useLoadableQuery.FetchPolicy`</td></tr> <tr><td>`LoadableQueryHookOptions`</td><td>`useLoadableQuery.Options`</td></tr> <tr><td>`LoadQueryFunction`</td><td>`useLoadableQuery.LoadQueryFunction`</td></tr> <tr><td>`UseLoadableQueryResult`</td><td>`useLoadableQuery.Result`</td></tr> <tr><td>`MutationFunctionOptions`</td><td>`useMutation.MutationFunctionOptions`</td></tr> <tr><td>`MutationHookOptions`</td><td>`useMutation.Options`</td></tr> <tr><td>`MutationResult`</td><td>`useMutation.Result`</td></tr> <tr><td>`MutationTuple`</td><td>`useMutation.ResultTuple`</td></tr> <tr><td>`BaseSubscriptionOptions`</td><td>`useSubscription.Options`</td></tr> <tr><td>`OnDataOptions`</td><td>`useSubscription.OnDataOptions`</td></tr> <tr><td>`OnSubscriptionDataOptions`</td><td>`useSubscription.OnSubscriptionDataOptions`</td></tr> <tr><td>`SubscriptionHookOptions`</td><td>`useSubscription.Options`</td></tr> <tr><td>`SubscriptionResult`</td><td>`useSubscription.Result`</td></tr> <tr><td>`QueryFunctionOptions`</td><td>`useQuery.Options`</td></tr> <tr><td>`QueryHookOptions`</td><td>`useQuery.Options`</td></tr> <tr><td>`QueryResult`</td><td>`useQuery.Result`</td></tr> <tr><td>`SuspenseQueryHookFetchPolicy`</td><td>`useSuspenseQuery.FetchPolicy`</td></tr> <tr><td>`SuspenseQueryHookOptions`</td><td>`useSuspenseQuery.Options`</td></tr> <tr><td>`UseSuspenseQueryResult`</td><td>`useSuspenseQuery.Result`</td></tr> <tr><td>`UseQueryRefHandlersResult`</td><td>`useQueryRefHandlers.Result`</td></tr> <tr><td>`UseFragmentOptions`</td><td>`useFragment.Options`</td></tr> <tr><td>`UseFragmentResult`</td><td>`useFragment.Result`</td></tr> <tr><td>`UseReadQueryResult`</td><td>`useReadQuery.Result`</td></tr> <tr><td>`UseSuspenseFragmentOptions`</td><td>`useSuspenseFragment.Options`</td></tr> <tr><td>`UseSuspenseFragmentResult`</td><td>`useSuspenseFragment.Result`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/react`</th><th>`@apollo/client/utilities/internal`</th></tr> <tr><td>`NoInfer`</td><td>(unchanged)</td></tr> <tr><td>`VariablesOption`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/react/internal`</th><th>`@apollo/client/react`</th></tr> <tr><td>`PreloadedQueryRef`</td><td>(unchanged)</td></tr> <tr><td>`QueryRef`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/testing`</th><th>(unchanged)</th></tr> <tr><td>`MockedRequest`</td><td>`MockLink.MockedRequest`</td></tr> <tr><td>`MockedResponse`</td><td>`MockLink.MockedResponse`</td></tr> <tr><td>`MockLinkOptions`</td><td>`MockLink.Options`</td></tr> <tr><td>`ResultFunction`</td><td>`MockLink.ResultFunction`</td></tr> </tbody><tbody> <tr><th>`@apollo/client/testing`</th><th>`@apollo/client/testing/react`</th></tr> <tr><td>`MockedProvider`</td><td>(unchanged)</td></tr> <tr><td>`MockedProviderProps`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/utilities`</th><th>`@apollo/client/utilities/internal`</th></tr> <tr><td>`argumentsObjectFromField`</td><td>(unchanged)</td></tr> <tr><td>`AutoCleanedStrongCache`</td><td>(unchanged)</td></tr> <tr><td>`AutoCleanedWeakCache`</td><td>(unchanged)</td></tr> <tr><td>`canUseDOM`</td><td>(unchanged)</td></tr> <tr><td>`checkDocument`</td><td>(unchanged)</td></tr> <tr><td>`cloneDeep`</td><td>(unchanged)</td></tr> <tr><td>`compact`</td><td>(unchanged)</td></tr> <tr><td>`createFragmentMap`</td><td>(unchanged)</td></tr> <tr><td>`createFulfilledPromise`</td><td>(unchanged)</td></tr> <tr><td>`createRejectedPromise`</td><td>(unchanged)</td></tr> <tr><td>`dealias`</td><td>(unchanged)</td></tr> <tr><td>`decoratePromise`</td><td>(unchanged)</td></tr> <tr><td>`DeepMerger`</td><td>(unchanged)</td></tr> <tr><td>`filterMap`</td><td>(unchanged)</td></tr> <tr><td>`getApolloCacheMemoryInternals`</td><td>(unchanged)</td></tr> <tr><td>`getApolloClientMemoryInternals`</td><td>(unchanged)</td></tr> <tr><td>`getDefaultValues`</td><td>(unchanged)</td></tr> <tr><td>`getFragmentDefinition`</td><td>(unchanged)</td></tr> <tr><td>`getFragmentDefinitions`</td><td>(unchanged)</td></tr> <tr><td>`getFragmentFromSelection`</td><td>(unchanged)</td></tr> <tr><td>`getFragmentQueryDocument`</td><td>(unchanged)</td></tr> <tr><td>`getGraphQLErrorsFromResult`</td><td>(unchanged)</td></tr> <tr><td>`getInMemoryCacheMemoryInternals`</td><td>(unchanged)</td></tr> <tr><td>`getOperationDefinition`</td><td>(unchanged)</td></tr> <tr><td>`getOperationName`</td><td>(unchanged)</td></tr> <tr><td>`getQueryDefinition`</td><td>(unchanged)</td></tr> <tr><td>`getStoreKeyName`</td><td>(unchanged)</td></tr> <tr><td>`graphQLResultHasError`</td><td>(unchanged)</td></tr> <tr><td>`hasDirectives`</td><td>(unchanged)</td></tr> <tr><td>`hasForcedResolvers`</td><td>(unchanged)</td></tr> <tr><td>`isArray`</td><td>(unchanged)</td></tr> <tr><td>`isDocumentNode`</td><td>(unchanged)</td></tr> <tr><td>`isField`</td><td>(unchanged)</td></tr> <tr><td>`isNonEmptyArray`</td><td>(unchanged)</td></tr> <tr><td>`isNonNullObject`</td><td>(unchanged)</td></tr> <tr><td>`isPlainObject`</td><td>(unchanged)</td></tr> <tr><td>`makeReference`</td><td>(unchanged)</td></tr> <tr><td>`makeUniqueId`</td><td>(unchanged)</td></tr> <tr><td>`maybeDeepFreeze`</td><td>(unchanged)</td></tr> <tr><td>`mergeDeep`</td><td>(unchanged)</td></tr> <tr><td>`mergeDeepArray`</td><td>(unchanged)</td></tr> <tr><td>`mergeOptions`</td><td>(unchanged)</td></tr> <tr><td>`omitDeep`</td><td>(unchanged)</td></tr> <tr><td>`preventUnhandledRejection`</td><td>(unchanged)</td></tr> <tr><td>`registerGlobalCache`</td><td>(unchanged)</td></tr> <tr><td>`removeDirectivesFromDocument`</td><td>(unchanged)</td></tr> <tr><td>`resultKeyNameFromField`</td><td>(unchanged)</td></tr> <tr><td>`shouldInclude`</td><td>(unchanged)</td></tr> <tr><td>`storeKeyNameFromField`</td><td>(unchanged)</td></tr> <tr><td>`stringifyForDisplay`</td><td>(unchanged)</td></tr> <tr><td>`toQueryResult`</td><td>(unchanged)</td></tr> <tr><td>`DecoratedPromise`</td><td>(unchanged)</td></tr> <tr><td>`DeepOmit`</td><td>(unchanged)</td></tr> <tr><td>`FragmentMap`</td><td>(unchanged)</td></tr> <tr><td>`FragmentMapFunction`</td><td>(unchanged)</td></tr> <tr><td>`FulfilledPromise`</td><td>(unchanged)</td></tr> <tr><td>`IsAny`</td><td>(unchanged)</td></tr> <tr><td>`NoInfer`</td><td>(unchanged)</td></tr> <tr><td>`PendingPromise`</td><td>(unchanged)</td></tr> <tr><td>`Prettify`</td><td>(unchanged)</td></tr> <tr><td>`Primitive`</td><td>(unchanged)</td></tr> <tr><td>`RejectedPromise`</td><td>(unchanged)</td></tr> <tr><td>`RemoveIndexSignature`</td><td>(unchanged)</td></tr> <tr><td>`VariablesOption`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/utilities/global`</th><th>`@apollo/client/utilities/environment`</th></tr> <tr><td>`DEV`</td><td>`__DEV__`</td></tr> <tr><td>`__DEV__`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/utilities/global`</th><th>`@apollo/client/utilities/internal/globals`</th></tr> <tr><td>`global`</td><td>(unchanged)</td></tr> <tr><td>`maybe`</td><td>(unchanged)</td></tr> </tbody><tbody> <tr><th>`@apollo/client/utilities/global`</th><th>`@apollo/client/utilities/invariant`</th></tr> <tr><td>`invariant`</td><td>(unchanged)</td></tr> <tr><td>`InvariantError`</td><td>(unchanged)</td></tr> <tr><td>`newInvariantError`</td><td>(unchanged)</td></tr> </tbody> </table> </details>If you used the Codemod to update your imports, all removed exports moved to the @apollo/client/v4-migration entry point. You should get a TypeScript error everywhere you use a removed export. When you hover over the export, you'll get more information about why the export was removed along with migration instructions.
For a list of all removed imports and recommended actions, see node_modules/@apollo/client/v4-migration.d.ts
ApolloClientSeveral of the constructor options of ApolloClient have changed in Apollo Client 4. This section provides instructions on migrating to the new options when initializing your ApolloClient instance.
HttpLinkThis change is performed by the codemod
The uri, headers, and credentials options used to implicitly create an HttpLink have been removed. You now need to create a new HttpLink instance and pass it to the link option of ApolloClient.
import {
ApolloClient,
InMemoryCache,
HttpLink,// [!code ++]
} from "@apollo/client/core";
const client = new ApolloClient({
// ...
link: new HttpLink({// [!code ++]
uri: "https://example.com/graphql",
credentials: "include",
headers: { "x-custom-header": "value" },
}),// [!code ++]
// ...
});
Although the previous options were convenient, it meant all Apollo Client instances were bundled with HttpLink, even if it wasn't used in the link chain. This change enables you to use different terminating links without the increase in bundle size needed to include HttpLink. Most applications that scale moved away from these options anyways as soon as more complex link chains were needed.
This change is performed by the codemod
The name and version options, which are a part of the client awareness feature, have been moved into the clientAwareness option.
const client = new ApolloClient({
// ...
clientAwareness: {// [!code ++]
name: "My Apollo Client App",
version: "1.0.0",
},// [!code ++]
// ...
});
This change reduced confusion on what the name and version options were meant for and provides better future-proofing for additional features that might be added later.
This option only has an effect if you are using the client-awareness-capable HttpLink or BatchHttpLink, or if you manually combine the ClientAwarenessLink with a compatible terminating link.
This change is performed by the codemod
When using @client fields, you now need to create a new LocalState instance and provide it as the localState option to the ApolloClient constructor.
import {
ApolloClient,
InMemoryCache,
LocalState,// [!code ++]
} from "@apollo/client/core";
const client = new ApolloClient({
// ...
localState: new LocalState(),// [!code ++]
// ...
});
Additionally, if you are using local resolvers with the resolvers option, you need to move the resolvers option to the LocalState constructor instead of the ApolloClient constructor.
import {
ApolloClient,
InMemoryCache,
LocalState,// [!code ++]
} from "@apollo/client/core";
const client = new ApolloClient({
// ...
localState: new LocalState({// [!code ++]
resolvers: {
Query: {
myField: () => "Hello World",
},
},
}),// [!code ++]
// ...
});
Previously, all Apollo Client instances were bundled with local state management functionality, even if you didn't use the @client directive. This change means local state management is now opt-in and reduces the size of your bundle when you aren't using this feature.
connectToDevToolsThis change is performed by the codemod
The connectToDevTools option has been replaced with a new devtools option that contains an enabled property.
const client = new ApolloClient({
// ...
connectToDevTools: true,// [!code --]
devtools: { enabled: true },// [!code ++]
// ...
});
disableNetworkFetchesThis change is performed by the codemod
The disableNetworkFetches option has been renamed to prioritizeCacheValues to better describe its behavior.
const client = new ApolloClient({
// ...
disableNetworkFetches: ..., // [!code --]
prioritizeCacheValues: ..., // [!code ++]
// ...
});
@defer)This change is performed by the codemod, however you may need to make some additional changes
The incremental delivery protocol implementation is now configurable and requires you to opt-in to use the proper format. If you currently use @defer in your application, you need to configure an incremental handler and provide it to the incrementalHandler option to the ApolloClient constructor.
Since Apollo Client 3 only supported an older version of the specification, initialize an instance of Defer20220824Handler and provide it as the incrementalHandler option.
import { Defer20220824Handler } from "@apollo/client/incremental";// [!code ++]
const client = new ApolloClient({
// ...
incrementalHandler: new Defer20220824Handler(),// [!code ++]
// ...
});
To provide accurate TypeScript types, you will also need to tell Apollo Client to use the types associated with the Defer20220824Handler in order to provide accurate types for incremental chunks, used with the ApolloLink.Result type. This ensures that you properly handle incremental results in your custom links that might access the result directly.
Create a new .d.ts file in your project (e.g. apollo-client.d.ts) and add the following content:
import { Defer20220824Handler } from "@apollo/client/incremental";// [!code highlight]
declare module "@apollo/client" {
export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {}// [!code highlight]
}
Apollo Client only provides support for the older incremental specification at this time. Future versions will introduce new incremental handlers to support more recent versions of the specification.
</Note>This change is performed by the codemod, however you may need to make some additional changes
In Apollo Client 4, the data masking types are now configurable to allow for more flexibility when working with different tools like GraphQL Code Generator or gql.tada - which might have different typings for data masking.
To configure the data masking types to be the same as they were in Apollo Client 3, create a .d.ts file in your project with the following content:
import { GraphQLCodegenDataMasking } from "@apollo/client/masking";// [!code highlight]
declare module "@apollo/client" {
export interface TypeOverrides// [!code highlight:2]
extends GraphQLCodegenDataMasking.TypeOverrides {}
}
You can combine multiple TypeOverrides of different kinds. For example, you can combine the data masking types with the Defer20220824Handler type overrides from the previous section.
import { Defer20220824Handler } from "@apollo/client/incremental";
import { GraphQLCodegenDataMasking } from "@apollo/client/masking";// [!code ++]
declare module "@apollo/client" {
export interface TypeOverrides extends Defer20220824Handler.TypeOverrides {}// [!code --]
export interface TypeOverrides// [!code ++:3]
extends Defer20220824Handler.TypeOverrides,
GraphQLCodegenDataMasking.TypeOverrides {}
}
notifyOnNetworkStatusChange default valueThe notifyOnNetworkStatusChange option now defaults to true. This affects React hooks that provide this option (e.g. useQuery) and ObservableQuery instances created by client.watchQuery.
This change means you might see loading states more often, especially when used with refetch or other APIs that cause fetches. If this causes issues, you can revert to the v3 behavior by setting the global default back to false.
new ApolloClient({
// ...
defaultOptions: {// [!code ++:5]
watchQuery: {
notifyOnNetworkStatusChange: false,
},
},
});
loading status emitted by ObservableQueryIn line with the change to the default notifyOnNetworkStatusChange value, the first value emitted from an ObservableQuery instance (created by client.watchQuery), sets loading to true when the query cannot be fulfilled by data in the cache. Previously, the initial loading state was not emitted.
const observable = client.watchQuery({ query: QUERY });
observable.subscribe({
next: (result) => {
// The first result emitted is the loading state
console.log(result);
/*
{
data: undefined,
dataState: "empty"
loading: true,
networkStatus: NetworkStatus.loading,
partial: false
}
*/
},
});
In Apollo Client 3, ObservableQuery instances were tracked by the client from the moment they were created until they were unsubscribed from. In cases where the ObservableQuery instance was never subscribed to, this caused memory leaks and unintended behavior with APIs such as refetchQueries when refetching active queries, because the ObservableQuery instances were never properly cleaned up.
In Apollo Client 4, ObservableQuery instances are now tracked by the client only when they are subscribed to until they are unsubscribed from. This allows for more efficient memory management by the garbage collector to free memory if the ObservableQuery instance is never subscribed to and no longer referenced by other code. Additionally, this avoids situations where refetchQueries might execute unnecessary network requests due to the tracking behavior.
As a result of this change, the definitions for active and inactive queries have changed.
ObservableQuery instances that don't have at least one subscriber are not tracked or accessible through the clientA query is in standby if it is skipped with the skip option or skipToken in a React hook, or if the fetchPolicy is set to standby.
This change affects the queries returned by client.getObservableQueries or the queries fetched by refetchQueries as these no longer include ObservableQuery instances without subscribers.
canonizeResults optionThe canonizeResults option has been removed because it caused memory leaks. If you use canonizeResults, you may see some changes to the object identity of some objects that were previously referentially equal.
useQuery(QUERY, {
canonizeResults: true,// [!code --]
});
Subscriptions are now deduplicated when queryDeduplication is enabled (the default). Subscriptions that qualify for deduplication attach to the same connection as a previously connected subscription.
To disable query deduplication, provide queryDeduplication: false to the context option when creating the subscription.
// with React's `useSubscription`
useSubscription(SUBSCRIPTION, {
context: { queryDeduplication: false },
});
// with the core API
const observable = client.subscribe({
query: SUBSCRIPTION,
context: { queryDeduplication: false },
});
Some GraphQL servers emit an initial value when the subscription first connects. A subscription that attaches to an existing connection because of deduplication does not receive an update until the server next emits a value. Depending on the frequency of updates pushed from the server, the deduplicated subscription might wait for an extended period of time before receiving its first value.
If you rely on the initial value to read data from the subscription, we recommend disabling query deduplication so that the subscription creates a new connection.
</Caution>ObservableQuery no longer inherits from ObservableObservableQuery instances returned from client.watchQuery no longer inherit from Observable. This might cause TypeScript errors when using ObservableQuery with some RxJS utilities that expect Observable instances, such as firstValueFrom.
Convert ObservableQuery to an Observable using the from function.
import { from } from "rxjs";
const observable = from(observableQuery);
// ^? Observable<ObservableQuery.Result<MaybeMasked<TData>>>
ObservableQuery implements the Subscribable interface, so you can call subscribe directly without converting to an Observable. It also implements the pipe function, so you can chain operators.
Use from only for functions that require an Observable instance.
Observable utilitiesApollo Client 3 included several utilities for working with Observable instances from the zen-observable library. Apollo Client 4 removes these utilities in favor of RxJS's operators.
fromErrorUse the throwError function instead.
const observable = fromError(new Error("Oops"));// [!code --]
const observable = throwError(() => {// [!code ++:3]
return new Error("Oops");
});
fromPromiseUse the from function instead.
const observable = fromPromise(promise);// [!code --]
const observable = from(promise);// [!code ++]
toPromiseUse the firstValueFrom function instead.
const promise = toPromise(observable);// [!code --]
const promise = firstValueFrom(observable);// [!code ++]
Error handling in Apollo Client 4 has changed significantly to be more predictable and intuitive.
<Tip>We recommend reading the Error handling guide which lists all of the available error classes and provides details on how to check for specific kinds of errors. The following sections only detail the necessary requirements to migrate your existing code but does not cover general error handling.
</Tip>error propertyIn Apollo Client 3, many result types returned both the error and errors properties. This includes the result emitted from ObservableQuery and some React hooks such as useQuery. This made it difficult to determine which property should be used to determine the source of the error. This was further complicated by the fact that the errors property was set only when errorPolicy was all!
Apollo Client 4 unifies all errors into a single error property. If you check for errors, remove this check and use error instead.
const { error, errors } = useQuery(QUERY);// [!code --]
const { error } = useQuery(QUERY);// [!code ++]
if (error) {
// ...
} else if (errors) {// [!code --:2]
// ...
}
ApolloErrorIn Apollo Client 3, every error was wrapped in an ApolloError instance. This made it difficult to debug the source of the error, especially when reported by error tracking services, because the stack trace pointed to the ApolloError class. Additionally, it was unclear whether error types wrapped in ApolloError were mutually exclusive because it contained graphQLErrors, protocolErrors, networkError, and clientErrors properties that were all optional.
Apollo Client 4 removes the ApolloError class entirely.
graphQLErrorsGraphQL errors are now encapsulated in a CombinedGraphQLErrors instance. You can access the raw GraphQL errors with the errors property. To migrate, use CombinedGraphQLErrors.is(error) to first check if the error is caused by one or more GraphQL errors, then access the errors property.
import { CombinedGraphQLErrors } from "@apollo/client"; // [!code ++]
const { error } = useQuery(QUERY);
if (error.graphQLErrors) { // [!code --]
error.graphQLErrors.map((graphQLError) =>); // [!code --]
if (CombinedGraphQLErrors.is(error)) { // [!code ++]
error.errors.map((graphQLError) =>); // [!code ++]
}
networkErrorNetwork errors are no longer wrapped and instead returned as-is. This makes it easier to debug the source of the error as the stack trace now points to the location of the error.
To migrate, use the error property directly.
const { error } = useQuery(QUERY);
if (error.networkError) { // [!code --]
console.log(error.networkError.message); // [!code --]
if (error) { // [!code ++]
console.log(error.message); // [!code ++]
}
protocolErrorsProtocol errors are now encapsulated in a CombinedProtocolErrors instance. You can access the raw protocol errors with the errors property. To migrate, use CombinedProtocolErrors.is(error) to first check if the error is caused by one or more protocol errors, then access the errors property.
import { CombinedProtocolErrors } from "@apollo/client"; // [!code ++]
const { error } = useQuery(QUERY);
if (error.protocolErrors) { // [!code --]
error.graphQLErrors.map((graphQLError) =>); // [!code --]
if (CombinedProtocolErrors.is(error)) { // [!code ++]
error.errors.map((graphQLError) =>); // [!code ++]
}
clientErrorsThe clientErrors property was not used by Apollo Client and therefore has no replacement. Any non-GraphQL errors or non-protocol errors are passed through as-is.
Apollo Client 4 guarantees that the error property is an ErrorLike object, an object with a message and name property. This avoids the need to check the type of error before consuming it. To make such a guarantee, thrown non-error-like values are wrapped.
Strings thrown as errors are wrapped in an Error instance for you. The string is set as the error's message.
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);
// `error` is `new Error("Test error")`
console.log(error.message);
All other object types (e.g. symbols, arrays, etc.) or primitives thrown as errors are wrapped in an UnconventionalError instance with the cause property set to the original object. Additionally, UnconventionalError sets its name to UnconventionalError and its message to "An error of unexpected shape occurred.".
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({ message: "Not a proper error type" });
});
}),
});
// ...
const { error } = useQuery(query);
// `error` is an `UnconventionalError` instance
console.log(error.message); // => "An error of unexpected shape occurred."
// The `cause` returns the original object that was thrown
console.log(error.cause); // => { message: "Not a proper error type" }
errorPolicyApollo Client 3 always treated network errors as if the errorPolicy was set to none. This meant network errors were always thrown even if a different errorPolicy was specified.
Apollo Client 4 unifies the behavior of network errors and GraphQL errors so that network errors now adhere to the errorPolicy. Network errors now resolve promises when the errorPolicy is set to anything other than none.
errorPolicy: allThe promise is resolved. The error is accessible on the error property.
const { data, error } = await client.query({ query, errorPolicy: "all" });
console.log(data); // => undefined
console.log(error); // => new Error("...")
Network errors always set data to undefined.
errorPolicy: ignoreThe promise is resolved and the error is ignored.
const { data, error } = await client.query({ query, errorPolicy: "ignore" });
console.log(data); // => undefined
console.log(error); // => undefined
data is always set to undefined because a GraphQL result wasn't returned.
ObservableQuery or subscription ObservablesIn Apollo Client 3, network errors emitted an error notification on the ObservableQuery instance. Because error notifications terminate observables, this meant you needed special handling to restart the ObservableQuery instance so that it listened for new results. Network errors were emitted to the error callback.
Apollo Client 4 changes this and no longer emits an error notification. This ensures ObservableQuery doesn't terminate on errors and can continue listening for new results, such as calling refetch after an error.
Access the error on the error property emitted in the next callback. It is safe to remove the error callback entirely as it is no longer used.
const observable = client.watchQuery({ query });
observable.subscribe({
next: (result) => {
if (result.error) {// [!code ++:3]
// handle error
}
// ...
},
error: (error) => {// [!code --:3]
// handle error
},
});
The change applies to all Apollo Client observables, including subscriptions created with client.subscribe(). GraphQL errors in subscriptions are always delivered through the next callback in result.error, never through the error callback. The subscription only terminates for non-recoverable errors like network failures.
ServerError and ServerParseError are error classesApollo Client 3 threw special ServerError and ServerParseError types when the HTTP response was invalid. These types were plain Error instances with some added properties.
Apollo Client 4 turns these types into proper error classes. You can check for these types using the .is static method available on each error class.
const { error } = useQuery(QUERY);
if (ServerError.is(error)) {
// handle the server error
}
if (ServerParseError.is(error)) {
// handle the server parse error
}
ServerError no longer parses the response text as JSONWhen a ServerError was thrown, such as when the server returned a non-2xx status code, Apollo Client 3 tried to parse the raw response text into JSON to determine if a valid GraphQL response was returned. If successful, the parsed JSON value was set as the result property on the error.
Apollo Client 4 more strictly adheres to the GraphQL over HTTP specification and does away with the automatic JSON parsing. As such, the result property is no longer accessible and is replaced by the bodyText property which contains the value of the raw response text.
If you relied on the automatic JSON parsing, you will need to JSON.parse the bodyText instead.
const { error } = useQuery(QUERY);
if (ServerError.is(error)) {
try {
const json = JSON.parse(error.bodyText);
} catch (e) {
// handle invalid JSON
}
}
Apollo Client 4 is more opinionated about how to use it. We believe that React hooks should be used to synchronize data with your component and avoid side effects that don't achieve this goal. As a result, we now recommend the use of core APIs directly in several use cases where synchronization with a component is not needed or intended.
As an example, if you used useLazyQuery to execute queries but don't read the state values returned in the result tuple, we recommend that you now use client.query directly. This avoids unnecessary re-renders in your component for state that you don't consume.
const [execute] = useLazyQuery(MY_QUERY);// [!code --:3]
// ...
await execute({ variables });
const client = useApolloClient();// [!code ++:3]
// ...
await client.query({ query: MY_QUERY, variables });
useLazyQueryuseLazyQuery has been rewritten to have more predictable and intuitive behavior based on years of user feedback.
In Apollo Client 3, the useLazyQuery behaved like the useQuery hook after the execute function was called the first time. Changes to options often triggered unintended network requests as a result of this behavior.
In Apollo Client 4, useLazyQuery focuses on user interaction and more predictable data synchronization with the component. Network requests are now initiated only when the execute function is called. This makes it safe to re-render your component with new options provided to useLazyQuery without the unintended network requests. New options are used the next time the execute function is called.
variables and context optionsThe useLazyQuery hook no longer accepts the variables or context options. These options have been moved to the execute function instead. This removes the variable merging behavior which was confusing and hard to predict with the new behavior.
To migrate, move the variables and context options from the hook to the execute function:
const [execute, { data }] = useLazyQuery(QUERY, {
variables:,// [!code --]
context: { /* ... */},// [!code --]
});
function onUserInteraction(){
execute({
variables:, // [!code ++]
context:, // [!code ++]
})
}
The execute function no longer accepts any other options than variables and context. Those options should be passed directly to the hook instead.
const [execute, { data }] = useLazyQuery(QUERY, {
fetchPolicy: "no-cache", // [!code ++]
});
// ...
function onUserInteraction(){
execute({
fetchPolicy: "no-cache", // [!code --]
})
}
If you need to change the value of an option, rerender the component with the new option value before calling the execute function again.
When calling the execute function multiple times, the fetchPolicy is not reset to initialFetchPolicy between executions. This means that subsequent calls to execute continue using the fetch policy from the previous execution. This primarily affects you if you're using nextFetchPolicy to change the fetchPolicy after the initial fetch.
Calling the execute function of useLazyQuery during render is no longer allowed and will now throw an error.
function MyComponent() {
const [execute, { data }] = useLazyQuery(QUERY);
// This throws an error
execute();
return /* ... */;
}
We recommend instead migrating to useQuery which executes the query during render automatically.
function MyComponent() {
const [execute, { data }] = useLazyQuery(QUERY);// [!code --:2]
execute();
const { data } = useQuery(QUERY);// [!code ++]
return /* ... */;
}
We don't recommend calling the execute function in a useEffect hook as a replacement. Instead, migrate to useQuery. If you need to hold the execution of the query beyond the initial render, consider using the skip option.
function MyComponent({ dependentValue }) {
const [execute, { data }] = useLazyQuery(QUERY);// [!code --]
useEffect(() => {// [!code --:5]
if (dependentValue) {
execute();
}
}, [dependentValue]);
const { data } = useQuery(QUERY, { skip: !dependentValue });// [!code ++]
return /* ... */;
}
Calling execute inside a useEffect delays the execution of the query unnecessarily. Reserve the use of useLazyQuery for user interaction in callback functions.
This change also means that it is no longer possible to make queries with useLazyQuery during SSR. If you need to execute the query during render, use useQuery instead.
function MySSRComponent() {
const [execute, { data, loading, error }] = useLazyQuery(QUERY);// [!code --]
execute();// [!code --]
const { data, loading, error } = useQuery(QUERY);// [!code ++]
// ...
}
onCompleted and onError callbacksThe onCompleted and onError callbacks have been removed from the hook options. If you need to execute a side effect when the query completes, await the promise returned by the execute function and perform side effects there.
const [execute] = useLazyQuery(QUERY, {
onCompleted(data) { /* handle success */ }, // [!code --]
onError(error) { /* handle error */ }, // [!code --]
});
async function onUserInteraction(){
await execute({ variables })// [!code --]
try {// [!code ++:6]
const { data } = await execute({ variables })
// handle success
} catch (error) {
// handle error
}
}
In-flight queries are now aborted more frequently. This occurs under two conditions:
execute function while a previous query is in flightIn each of these cases, the promise returned by the execute function is rejected with an AbortError. This change means it is no longer possible to run multiple queries simultaneously.
In some cases, you may need the query to run to completion, even if a new query is started or your component unmounts. In these cases, you can call the .retain() function on the promise returned by the execute function.
const [execute] = useLazyQuery(QUERY);
async function onUserInteraction() {
try {
const { data } = await execute({ variables });// [!code --]
const { data } = await execute({ variables }).retain();// [!code ++]
} catch (error) {}
}
Even though in-flight queries run to completion when using .retain(), the result returned from the hook only reflects state from the latest call of the execute function. Any previously retained queries are not synchronized to the hook state.
Usage of .retain() can be a sign that you are using useLazyQuery to trigger queries and are not interested in synchronizing the result with your component. In that case, we recommend using client.query directly.
const [execute] = useLazyQuery(QUERY);// [!code --]
const client = useApolloClient();// [!code ++]
async function onUserInteraction() {
try {
const { data } = await execute({ variables }).retain();// [!code --]
const { data } = await client.query({ query: QUERY, variables });// [!code ++]
} catch (error) {}
}
execute functionThe promise returned from the execute function is now consistently resolved or rejected based on your configured errorPolicy when errors are returned. With an errorPolicy of none, the promise always rejects. With an errorPolicy of all, the promise always resolves and sets the error property on the result. With an errorPolicy of ignore, the promise always resolves and discards the errors.
Previously, Apollo Client 3 resolved the promise when GraphQL errors were returned when using an errorPolicy of none. Network errors always caused the promise to reject, regardless of your configured error policy. This change unifies the error behavior across all error types to ensure errors are handled consistently.
To migrate code that uses an errorPolicy of none, wrap the call to execute in a try/catch block and use CombinedGraphQLErrors.is(error) to check for GraphQL errors.
const [execute] = useLazyQuery(QUERY, {
errorPolicy: "none",
});
async function onUserInteraction() {
const { data, error } = await execute({ variables });// [!code --:4]
if (error.graphQLErrors) {
// handle GraphQL errors
}
try {// [!code ++:6]
const { data } = await execute({ variables });
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
// handle GraphQL errors
}
}
}
To migrate code that uses an errorPolicy of all, read the error from the error property returned in the result. You can optionally remove any try/catch statements because the promise resolves for all error types.
const [execute] = useLazyQuery(QUERY, {
errorPolicy: "all",
});
async function onUserInteraction() {
try {// [!code --:7]
const { data } = await execute({ variables });
} catch (error) {
if (error.graphQLErrors) {
// handle GraphQL errors
}
}
const { data, error } = await execute({ variables });// [!code ++:4]
if (CombinedGraphQLErrors.is(error)) {
// handle GraphQL errors
}
}
useMutationuseMutation has been modified to work with our philosophy that React hooks should be used to synchronize hook state with your component.
ignoreResults optionThe ignoreResults option of useMutation has been removed. If you want to trigger a mutation, but are not interested in synchronizing the result with your component, use client.mutate directly.
const [mutate] = useMutation(MY_MUTATION, { ignoreResults: true });// [!code --]
const client = useApolloClient();// [!code ++]
// ...
await mutate({ variables });// [!code --]
await client.mutate({ mutation: MY_MUTATION, variables });// [!code ++]
useQuerynotifyOnNetworkStatusChange default valueThe notifyOnNetworkStatusChange option now defaults to true. This change means you might see loading states more often, especially when used with refetch or other APIs that cause fetches. If this causes issues, you can revert to the v3 behavior by setting the global default back to false.
new ApolloClient({
// ...
defaultOptions: {// [!code ++:5]
watchQuery: {
notifyOnNetworkStatusChange: false,
},
},
});
onCompleted and onError callbacksThe onCompleted and onError callback options have been removed. Their behavior was ambiguous and made it easy to introduce subtle bugs in your application.
You can read more about this decision and recommendations on what to do instead in the related GitHub issue.
preloadQuerytoPromise moved from queryRef to the preloadQuery functionThe toPromise method now exists as a property on the preloadQuery function instead of on the queryRef object. This change makes queryRef objects more serializable for SSR environments.
To migrate, call preloadQuery.toPromise and pass it the queryRef.
function loader() {
const queryRef = preloadQuery(QUERY, options);
return queryRef.toPromise();// [!code --]
return preloadQuery.toPromise(queryRef);// [!code ++]
}
Apollo Client 4 comes with a lot of TypeScript improvements to the hook types. If you use generated hooks from a tool such as GraphQL Codegen's @graphql-codegen/typescript-react-apollo plugin, you will not benefit from these improvements since the generated hooks are missing the necessary function overloads to provide critical type safety. We recommend that you stop using generated hooks immediately and create a migration strategy to move away from them.
Use the hooks exported from Apollo Client directly with TypedDocumentNode instead.
The GraphQL Codegen team has created a codemod that helps you migrate away from generated hooks. Learn more about migrating your code using the new codemod by watching the tutorial video.
Note that the codemod is a separate package and not maintained by the Apollo Client team. Generally, we recommend that you use a manual codegen configuration over the client preset as shown in the video because the client preset includes some features that are incompatible with Apollo Client. If you choose to use the client preset, see our recommended configuration for the client preset. In the long term, consider using the plugins directly with our recommended starter configuration.
</Note>Most types are now colocated with the API that they belong to. This makes them more discoverable, adds consistency to the naming of each type, and provides clear ownership boundaries.
Some examples:
FetchResult is now ApolloLink.ResultApolloClientOptions is now ApolloClient.OptionsQueryOptions is now ApolloClient.QueryOptionsApolloQueryResult is now ObservableQuery.ResultQueryHookOptions is now useQuery.OptionsQueryResult is now useQuery.ResultLazyQueryResult is now useLazyQuery.ResultMany of the old, most-used types are still available in Apollo Client 4 (including the examples above), but are now deprecated in favor of the new namespaced types. We suggest running the codemod to update your code to use the new namespaced types.
<TContext> generic argumentMany APIs in Apollo Client 3 provided a TContext generic argument that allowed you to provide the type for the context option. However, this generic argument was not consistently applied across all APIs that provided a context option and was easily forgotten.
Apollo Client 4 removes the TContext generic argument in favor of using declaration merging with the DefaultContext interface to provide types for the context option. This provides type safety throughout all Apollo Client APIs when using context.
To define types for your custom context properties, create a TypeScript file and define the DefaultContext interface.
// This import is necessary to ensure all Apollo Client imports
// are still available to the rest of the application.
import "@apollo/client";
declare module "@apollo/client" {
interface DefaultContext {
myProperty?: string;
requestId?: number;
}
}
<TCacheShape> generic argumentThe TCacheShape generic argument has been removed from the ApolloClient constructor. Most users set this type to any which provided little benefit for type safety. APIs that previously relied on TCacheShape are now set to unknown.
To migrate, remove the TCacheShape generic argument when initializing ApolloClient.
new ApolloClient<any>({ // [!code --]
new ApolloClient({ // [!code ++]
// ...
});
<TSerialized> generic argumentThe TSerialized generic argument has been removed from the ApolloCache abstract class. Most users set this type to any which provided little benefit for type safety. APIs that previously relied on TSerialized are now set to unknown.
To migrate, remove the TSerialized generic argument when referencing ApolloCache.
const cache: ApolloCache<any> = ... // [!code --]
const cache: ApolloCache = ... // [!code ++]
All links are now available as classes. The old link creator functions are still provided, but are now deprecated and will be removed in a future major version.
| Old link creator | New link class |
|---|---|
setContext | SetContextLink |
onError | ErrorLink |
createHttpLink | HttpLink |
createPersistedQueryLink | PersistedQueryLink |
removeTypenameFromVariables | RemoveTypenameFromVariablesLink |
Pay attention when migrating from the old setContext creator to the new SetContextLink class.
The argument order for the callback function has changed. To migrate, swap the order of the prevContext and operation arguments.
import { setContext } from "@apollo/client/link/context"; // [!code --]
import { SetContextLink } from "@apollo/client/link/context"; // [!code ++]
const authLink = setContext((operation, prevContext) => { /*...*/ } // [!code --]
const authLink = new SetContextLink((prevContext, operation) => { /*...*/ } // [!code ++]
empty, from, concat, and split functionsThis change is performed by the codemod
The bare empty, from, concat, and split functions exported from @apollo/client and @apollo/client/link are now deprecated in favor of using the equivalent static methods on the ApolloLink class.
import { empty, from, concat, split } from "@apollo/client";// [!code --]
import { ApolloLink } from "@apollo/client";// [!code ++]
const link = empty();// [!code --]
const link = ApolloLink.empty();// [!code ++]
from([linkA, linkB]);// [!code --]
ApolloLink.from([linkA, linkB]);// [!code ++]
concat(linkA, linkB);// [!code --]
ApolloLink.concat(linkA, linkB);// [!code ++]
split((operation) => check(operation), linkA, linkB);// [!code --]
ApolloLink.split((operation) => check(operation), linkA, linkB);// [!code ++]
concat behaviorThe static ApolloLink.concat method and the ApolloLink.prototype.concat method now accept a dynamic number of arguments to allow for concatenation with more than one link. This aligns the API with more familiar APIs such as Array.prototype.concat which accepts a variable number of arguments and provides more succinct code.
link1.concat(link2).concat(link3);// [!code --]
link1.concat(link2, link3);// [!code ++]
ApolloLink.concatThe static ApolloLink.concat is now deprecated in favor of ApolloLink.from. With the change to allow for a variable number of arguments, ApolloLink.concat(a, b) is now an alias for ApolloLink.from([a, b]) and provides no additional benefit. ApolloLink.concat will be removed in a future major version.
import { ApolloLink } from "@apollo/client/link";
ApolloLink.concat(firstLink, secondLink);// [!code --]
ApolloLink.from([firstLink, secondLink]);// [!code ++]
from,concat,split static and instance methods now require ApolloLink instancesThese methods previously accepted ApolloLink instances or plain request handler functions as arguments. These APIs have been updated to require ApolloLink instances.
To migrate, wrap the request handler functions in an ApolloLink instance.
ApolloLink.from(
(operation, forward) => {// [!code --:3]
/*...*/
},
new ApolloLink((operation, forward) => {// [!code ++:3]
/*...*/
}),
nextLink
);
ErrorLinkThe ErrorLink (previously created with onError) now uses a single error property instead of separate graphQLErrors, networkError, and protocolErrors.
With built-in error classes, use ErrorClass.is to check for specific error types.
For external error types like TypeError, use instanceof or the more modern Error.isError in combination with a check for error.name to check the kind of error.
import {
onError,// [!code --]
ErrorLink,// [!code ++]
} from "@apollo/client/link/error";
import { CombinedGraphQLErrors, ServerError } from "@apollo/client";// [!code ++]
const errorLink = onError(// [!code --:12]
({ graphQLErrors, networkError, protocolErrors, response }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message }) =>
console.log(`GraphQL error: ${message}`)
);
}
if (networkError) {
console.log(`Network error: ${networkError.message}`);
}
}
);
const errorLink = new ErrorLink(({ error, result }) => {// [!code ++:11]
if (CombinedGraphQLErrors.is(error)) {
error.errors.forEach(({ message }) =>
console.log(`GraphQL error: ${message}`)
);
} else if (ServerError.is(error)) {
console.log(`Server error: ${error.message}`);
} else if (error) {
console.log(`Other error: ${error.message}`);
}
});
If the error thrown in the link chain is not an error-like object, it will be wrapped in an UnconventionalError class - so it is always safe to access error.message and error.name on the error object.
operationoperationName as undefined for anonymous queriesThe operationName is now set to undefined when executing an anonymous query. Anonymous queries were previously set as an empty string.
If you use string methods on the operationName, you may need to check for undefined first.
operation.operationType propertyIn some cases, you might have needed to determine the GraphQL operation type of the request. This is common when using ApolloLink.split to route subscription operations to a WebSocket-based link.
A new operationType property has been introduced to reduce the boilerplate needed to determine the operation type of the GraphQL document.
import { getMainDefinition } from "@apollo/client";// [!code --]
import { OperationTypeNode } from "graphql";// [!code ++]
new ApolloLink((operation, forward) => {
const definition = getMainDefinition(query);// [!code --:4]
const isSubscription =
definition.kind === "OperationDefinition" &&
definition.operation === "subscription";
const isSubscription =// [!code ++:2]
operation.operationType === OperationTypeNode.SUBSCRIPTION;
});
The context type can be extended by using declaration merging to allow you to define custom properties used by your links.
For example, to make your context type-safe for usage with HttpLink, place this snippet in a .d.ts file in your project:
import { HttpLink } from "@apollo/client";
declare module "@apollo/client" {
interface DefaultContext extends HttpLink.ContextOptions {}
}
For more information on how to setup context types, see the TypeScript guide.
operation.getContextThe context object returned by operation.getContext() is now frozen and Readonly. This prevents mutations to the context object that aren't propagated to downstream links and could cause subtle bugs.
To make changes to context, use operation.setContext().
cache propertyApollo Client 3 provided a reference to the cache on the context object for use with links via the cache property. This property has been removed and replaced with a client property on the operation which references the client instance that initiated the request.
Access the cache through the client property instead.
new ApolloLink((operation, forward) => {
const cache = operation.getContext().cache;// [!code --]
const cache = operation.client.cache;// [!code ++]
});
getCacheKey functionApollo Client 3 provided a getCacheKey function on the context object to obtain cache IDs. This function has been removed.
Use the client property to get access to the cache.identify function to identify an object.
new ApolloLink((operation, forward) => {
const cacheID = operation.getContext().getCacheKey(obj);// [!code --]
const cacheID = operation.client.cache.identify(obj);// [!code ++]
});
execute now requires the clientIf you use the execute function directly to run the link chain, the execute function now requires a 3rd argument that provides the ApolloClient instance that represents the client that initiated the request.
Provide a context argument with the client option to the 3rd argument of the execute function.
import { execute } from "@apollo/client";
const client = new ApolloClient({
// ...
});
execute(
link,
{ query /* ... */ },
{ client }// [!code ++]
);
If you use the execute function to unit-test custom links, provide a dummy ApolloClient instance. The ApolloClient instance does not need any special configuration unless your unit tests require it.
In Apollo Client 4, the underlying Observable implementation has changed from using zen-observable to using rxjs Observable instances.
This can affect custom link implementations that rely on the Observable API.
If you were previously using the map, filter, reduce, flatMap or concat methods on the Observable instances, you will need to update your code to use the rxjs equivalents.
Additionally, if you were using Observable.of or Observable.from, you will need to use the rxjs of or from functions instead.
import { of, from, map } from "rxjs";// [!code ++]
Observable.of(1, 2, 3);// [!code --]
of(1, 2, 3);// [!code ++]
Observable.from([1, 2, 3]);// [!code --]
from([1, 2, 3]);// [!code ++]
observable.map((x) => x * 2);// [!code --]
observable.pipe(map((x) => x * 2));// [!code ++]
A good way to find the operator you need is to follow the rxjs operator decision tree.
The HttpLink and BatchHttpLink links have been updated to add stricter compatibility with the GraphQL over HTTP specification.
This change means:
Accept header is now application/graphql-response+json,application/json;q=0.9 for all outgoing requestsapplication/graphql-response+json media type is now supported and the client handles the response according to the application/graphql-response+json behaviorapplication/json media type behaves according to the application/json behaviorThe client now does the following when application/json media type is returned.
ServerError when the server encodes a content-type using application/json and returns a non-200 status codeServerError when the server encodes using any other content-type and returns a non-200 status codeIf 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 by default.
Local state management (using @client fields) has been removed from core and is now an opt-in feature. This change helps reduce the bundle size of your application if you don't use local state management features.
To opt-in to use local state management with the @client directive, initialize an instance of LocalState and provide it as the localState option to the ApolloClient constructor. Apollo Client will throw an error if a @client field is used and localState is not provided.
import { LocalState } from "@apollo/client/local-state";// [!code ++]
new ApolloClient({
// ...
localState: new LocalState(),// [!code ++]
// ...
});
The resolvers option has been removed from the ApolloClient constructor and is now part of the LocalState class. Move local resolvers to the LocalState class.
new ApolloClient({
resolvers: {...}, // [!code --]
localState: new LocalState({
resolvers: {...} // [!code ++]
})
});
Now that local resolvers are part of the LocalState class, the following resolver methods were removed from the ApolloClient class:
addResolversgetResolverssetResolversIf you add resolvers after the initialization of the client, you may continue to do so with your LocalState instance using the addResolvers method.
const client = new ApolloClient({
// ...
});
client.addResolvers(resolvers);// [!code --]
client.localState.addResolvers(resolvers);// [!code ++]
getResolvers and setResolvers have been removed without a replacement. Stop using them.
const client = new ApolloClient({
// ...
});
const resolvers = client.getResolvers();// [!code --]
client.setResolvers(resolvers);// [!code --]
The context argument (i.e. the 3rd argument) passed to resolver functions has been updated. Apollo Client 3 spread request context and replaced any passed-in client and cache properties.
Apollo Client 4 moves the request context to the requestContext key to avoid name clashes and updates the shape of the object. The context argument is now provided with the following object:
{
// 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"
}
If you accessed properties from the request context, you will now need to read those properties from the requestContext key. If you used the cache property, you will need to get the cache from the client property.
new LocalState({
resolvers: {
MyType: {
myLocalField: (parent, args, { someValue, cache }) { // [!code --]
myLocalField: (parent, args, { requestContext, client }) { // [!code ++]
const someValue = requestContext.someValue; // [!code ++]
const cache = client.cache; // [!code ++]
}
}
}
})
Errors thrown in local resolvers are now handled properly and predictably. Errors are now added to the errors array in the GraphQL response and the field value is set to null.
The following example throws an error in a local resolver.
new LocalState({
resolvers: {
Query: {
localField: () => {
throw new Error("Could not get localField");
},
},
},
});
This results in the following result:
{
data: {
localField: null,
},
errors: [
{
message: "Could not get localField",
path: ["localField"],
extensions: {
localState: {
resolver: "Query.localField",
cause: new Error("Could not get localField"),
},
},
},
],
};
As a result of this change, it is now safe to throw, or allow thrown errors in your resolvers. If your local resolvers catch errors to avoid issues in Apollo Client 3, these can be removed in cases where you want the error to be returned in the GraphQL result.
new LocalState({
resolvers: {
Query: {
localField: () => {
try {// [!code --]
throw new Error("Could not get localField");
} catch (e) {// [!code --]
console.log(e);// [!code --]
}// [!code --]
},
},
},
});
Apollo Client 4 now checks the return value of local resolvers to ensure it returns a value or null. undefined is no longer a valid value.
When undefined is returned from a local resolver, the value is set to null and a warning is logged to the console.
This example returns undefined due to the early return.
new LocalState({
resolvers: {
Query: {
localField: () => {
if (someCondition) {
return;
}
return "value";
},
},
},
});
This results in the following result:
{
data: {
localField: null,
},
};
And a warning is emitted to the console:
The 'Query.localField' resolver returned
undefinedinstead of a value. This is likely a bug in the resolver. If you didn't mean to return a value, returnnullinstead.
Instead, the local resolver should return null instead.
if (someCondition) {
return;// [!code --]
return null;// [!code ++]
}
The exception is when running local resolvers to get values for exported variables for fields marked with the @export directive. For this case, you may return undefined to omit the variable from the outgoing request. If you need to know whether to return undefined or null, use the phase property provided to the context argument.
new LocalState({
resolvers: {
Query: {
localField: (parent, args, { phase }) => {
// `phase` is "resolve" when resolving the field for the response [!code highlight:4]
return phase === "resolve" ? null : undefined;
// `phase` is "exports" when getting variables for `@export` fields.
return phase === "exports" ? undefined : null;
},
},
},
});
__typenameApollo Client 4 now validates that a __typename property is returned from resolvers that return arrays or objects on non-scalar fields. If __typename is not included, an error is added to the errors array and the field value is set to null. This change ensures the object is cached properly to avoid bugs that might otherwise be difficult to track down.
If your local resolver returns an object or an array for a non-scalar field, make sure it includes the __typename field.
const query = gql`
query {
localUser @client {
id
}
}
`;
new LocalState({
resolvers: {
Query: {
localUser: () => ({
__typename: "LocalUser",// [!code ++]
// ...
}),
},
},
});
@export fieldsApollo Client 4 adds additional validation to fields and resolvers that use the @export directive before the request is sent to the server.
Each @export field is now checked to ensure it can be associated with a variable definition in the GraphQL document. If the @export directive doesn't include the as argument, or the GraphQL document doesn't define a variable definition that matches the name provided to the as argument, a LocalStateError is thrown.
You will need to ensure the GraphQL document includes variable definitions for all @export fields.
// This throws because "someVar" is not defined
const query = gql`
query {
field @client @export(as: "someVar")
}
`;
// This is valid
const query = gql`
query ($someVar: String) {
field @client @export(as: "someVar")
}
`;
Resolvers that return values for required variables are now checked to ensure the value isn't nullable. If a resolver returns null or undefined for a required variable, a LocalStateError is thrown. This would otherwise cause an error on the server.
Local resolvers are not allowed to throw errors when resolving values for variables to avoid ambiguity on how to handle the error. If an error is thrown from a local resolver, it is wrapped in a LocalStateError and rethrown.
Since local resolvers are allowed to throw errors when resolving field data, you can use the phase property provided to the context argument to determine whether to rethrow the error or return a different value.
new LocalState({
resolvers: {
Query: {
localField: (parent, args, { phase }) => {
try {
throw new Error("Oops couldn't get local field");
} catch (error) {
// Omit the variable value in the request
if (phase === "exports") {
return;
}
// Rethrow the error to add it to the `errors` array
throw error;
}
},
},
},
});
Each validation error throws an instance of LocalStateError. You can check if an error is a result of a local state error by using LocalStateError.is method. The error instance provides additional information about the path in the GraphQL document that caused the error.
import { LocalStateError } from "@apollo/client";
// ...
const { error } = useQuery(QUERY);
if (LocalStateError.is(error)) {
console.log(error.path, error.message);
}
Apollo Client 3 allowed for custom fragment matchers using the fragmentMatcher option provided to the ApolloClient constructor. This made it possible to add your own custom logic to match fragment spreads used with client field selection sets. Fragment matching is now part of the cache with the fragmentMatches API.
Apollo Client 4 removes the fragmentMatcher option and the associated setLocalStateFragmentMatcher method that allows you to set a fragment matcher after the client was initialized. Remove the use of these APIs.
const client = new ApolloClient({
fragmentMatcher: (rootValue, typeCondition, context) => true,// [!code --]
});
client.setLocalStateFragmentMatcher(() => true);// [!code --]
If you're using InMemoryCache, you're all set. InMemoryCache implements fragmentMatches for you. We recommend checking your possibleTypes configuration to ensure it is up-to-date with your local schema.
If you're using a custom cache implementation, you will need to check if it meets the new requirements for custom cache implementations. LocalState requires that the cache implements the fragmentMatches API. If the custom cache does not implement fragmentMatches, an error is thrown.
A lot of utilities that were previously part of the @apollo/client/utilities and @apollo/client/testing packages have been removed. They were used for internal testing purposes and we are not using them anymore, so we removed them.
MockedProvider changesdelayIf no delay is specified in mocks, MockLink now defaults to the realisticDelay function, which uses a random delay between 20 and 50ms to ensure tests handle loading states.
If you want to restore the previous behavior of "no delay", you can set it via
MockLink.defaultOptions = {
delay: 0,
};
createMockClient and mockSingleLinkThe createMockClient and mockSingleLink utilities have been removed. Instead, you can now use the MockLink class directly to create a mock link and pass it into a new ApolloClient instance.
exports field in package.jsonApollo Client 4 now uses the exports field in its package.json to define which files are available for import. This means you should now be able to use import { ApolloClient } from "@apollo/client" instead of import { ApolloClient } from "@apollo/client/index.js" or import { ApolloClient } from "@apollo/client/main.cjs".
Apollo Client is now transpiled to target a since 2023, node >= 20, not dead target. This means that Apollo Client can now use modern JavaScript features without downlevel transpilation, which will generally result in better performance and smaller bundle size.
We also have stopped the practice of shipping polyfills.
Please note that we might bump the transpilation target to more modern targets in upcoming minor versions. See our versioning policy for more details on the supported environments.
If you are targeting older browsers or special environments, you might need to adjust your build configuration to transpile the Apollo Client library itself to a lower target, or polyfill the missing features yourself.
Previously, you had to set a global __DEV__ variable to false to disable development mode.
Now, development mode is primarily controlled by the development export condition of the package.json exports field. Most modern bundlers should now automatically pick correctly between the development and production version of Apollo Client based on your build environment.
If your build tooling does not support the development or production export condition, Apollo Client falls back to the previous behavior, meaning that you can still set the __DEV__ global variable to false to disable development mode in those cases.
Custom cache implementations must now implement the fragmentMatches method, which is required for fragment matching in Apollo Client 4.