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.
Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:
Apollo Client 4.0 completely reimagines error handling for better clarity and debugging:
The compiled hooks are built with React Compiler v19.1.0-rc.2 and include a runtime polyfill for compatibility with React 17+.
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.
#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.
#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.
#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
.
#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.
#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)),
);
#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.
#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> {
}
}
#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 }
);
#12809 e2a0be8
Thanks @jerelmiller! - Remove the TVariables
generic argument on the GraphQLRequest
type.
#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.
#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,
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.
#12712 bbb2b61
Thanks @jerelmiller! - An error is now thrown when trying to call fetchMore
on a cache-only
query.
#12712 bbb2b61
Thanks @jerelmiller! - cache-only
queries are no longer refetched when calling client.reFetchObservableQueries
when includeStandby
is true
.
#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.
#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.
#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
#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.
#12700 8e96e08
Thanks @phryneas! - Added a new Streaming
type that will mark data
in results while dataStatus
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;
}
}
#12499 ce35ea2
Thanks @phryneas! - Enable React compiler for hooks in ESM builds.
#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
.
createPersistedQueryLink({
- disable: ({ graphQLErrors, networkError }) => {
+ disable: ({ error }) => {
- if (graphQLErrors) {
+ if (CombinedGraphQLErrors.is(error)) {
}
- if (networkError) {
+ if (error) {
}
- if (networkError) {
+ if (ServerError.is(error)) {
}
});
The response
property has also been renamed to result
.
createPersistedQueryLink({
- disable: ({ response }) => {
+ disable: ({ result }) => {
}
}
});
#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 }) => {
});
#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(...)
import { HKT } from "@apollo/client/utilities";
// transform this type into a higher kinded type that can be evaulated at a later time
interface CustomMaskedType extends HKT {
arg1: unknown; // TData
return: CustomMaskedImplementation<this["arg1"]>;
}
// create an "implementation interface" for the types you want to override
export interface CustomDataMaskingImplementation {
Masked: CustomMaskedType;
// other possible keys: MaskedDocumentNode
, FragmentType
, MaybeMasked
and Unmasked
}
If you don't specify overrides, Apollo Client will still default to the GraphQL Codegen data masking implementation.
The types for that are also explicitly exported as the GraphQLCodegenDataMasking
namespace in @apollo/client/masking
.
#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.
#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.
#12556 c3fceda
Thanks @phryneas! - Removed getLastResult
, getLastError
and resetLastResults
from ObservableQuery
#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: { /* ... */ }
+ }),
});
#12614 d2851e2
Thanks @jerelmiller! - Remove local resolvers APIs from ApolloClient
in favor of localState
. Methods removed are:
addResolvers
getResolvers
setResolvers
setLocalStateFragmentMatcher
#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 {
public fragmentMatches(
fragment: InlineFragmentNode | FragmentDefinitionNode,
typename: string
): boolean {
return;
}
}
#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.
#12556 c3fceda
Thanks @phryneas! - Dropped the saveAsLastResult
argument from ObservableQuery.getCurrentResult
#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:
{
requestContext: TContextValue,
client: ApolloClient,
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;
}
}
}
});
#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(),
});
#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.
#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.
#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.
#12222 d1a9054
Thanks @jerelmiller! - Drop support for React 16.
#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.
#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.
#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.
#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
.
#12384 6aa6fd3
Thanks @jerelmiller! - Remove the iterateObserversSafely
utility function.
#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);
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) => {
observer.error("Test error");
});
}),
});
const { error } = useQuery(query);
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);
#12384 6aa6fd3
Thanks @jerelmiller! - Remove fromError
utility function. Use throwError
instead.
#12211 c2736db
Thanks @jerelmiller! - Remove the deprecated graphql
, withQuery
, withMutation
, withSubscription
, and withApollo
hoc components. Use the provided React hooks instead.
#12262 10ef733
Thanks @jerelmiller! - Remove itAsync
test utility.
#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) {
}
#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.
#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.
#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";
throwServerError(response, result, "error message");
throw new ServerError("error message", { response, result });
#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
).
#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.
#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();
setNotifyOnNetworkStatusChange(false);
}
}
#12254 0028ac0
Thanks @jerelmiller! - Changes the default Accept
header to application/graphql-response+json
.
#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.
#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.
#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.
#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.
#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
:
#12428 abed922
Thanks @jerelmiller! - Removes the urql
multipart subscriptions utilities. Use the native multipart subscriptions support in urql
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.
#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.
#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
.
#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.
#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.
#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
.
#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";
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.
const subscriptionObservable = client.subscribe(...);
subscriptionObservable.subscribe({
next: (value) => console.log(value)
});
#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.
#12384 6aa6fd3
Thanks @jerelmiller! - Remove toPromise
utility function. Use firstValueFrom
instead.
#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
.
#12211 c2736db
Thanks @jerelmiller! - Remove the deprecated Query
, Mutation
, and Subscription
components. Use the provided React hooks instead.
In upcoming v3.6.x and v3.7 (beta) releases, we will be completely overhauling our server-side rendering utilities (getDataFromTree
et al.), and introducing suspenseful versions of our hooks, to take full advantage of the new patterns React 18+ enables for data management libraries like Apollo Client.
InMemoryCache
now guarantees that any two result objects returned by the cache (from readQuery
, readFragment
, etc.) will be referentially equal (===
) if they are deeply equal. Previously, ===
equality was often achievable for results for the same query, on a best-effort basis. Now, equivalent result objects will be automatically shared among the result trees of completely different queries. This guarantee is important for taking full advantage of optimistic updates that correctly guess the final data, and for "pure" UI components that can skip re-rendering when their input data are unchanged.
@benjamn in #7439
Mutations now accept an optional callback function called onQueryUpdated
, which will be passed the ObservableQuery
and Cache.DiffResult
objects for any queries invalidated by cache writes performed by the mutation's final update
function. Using onQueryUpdated
, you can override the default FetchPolicy
of the query, by (for example) calling ObservableQuery
methods like refetch
to force a network request. This automatic detection of invalidated queries provides an alternative to manually enumerating queries using the refetchQueries
mutation option. Also, if you return a Promise
from onQueryUpdated
, the mutation will automatically await that Promise
, rendering the awaitRefetchQueries
option unnecessary.
@benjamn in #7827
Support client.refetchQueries
as an imperative way to refetch queries, without having to pass options.refetchQueries
to client.mutate
.
@dannycochran in #7431
Improve standalone client.refetchQueries
method to support automatic detection of queries needing to be refetched.
@benjamn in #8000
Fix remaining barriers to loading @apollo/client/core
as native ECMAScript modules from a CDN like esm.run. Importing @apollo/client
from a CDN will become possible once we move all React-related dependencies into @apollo/client/react
in Apollo Client 4.
@benjamn in #8266
InMemoryCache
supports a new method called batch
, which is similar to performTransaction
but takes named options rather than positional parameters. One of these named options is an onDirty(watch, diff)
callback, which can be used to determine which watched queries were invalidated by the batch
operation.
@benjamn in #7819
Allow merge: true
field policy to merge Reference
objects with non-normalized objects, and vice-versa.
@benjamn in #7778
Allow identical subscriptions to be deduplicated by default, like queries.
@jkossis in #6910
Always use POST
request when falling back to sending full query with @apollo/client/link/persisted-queries
.
@rieset in #7456
The FetchMoreQueryOptions
type now takes two instead of three type parameters (<TVariables, TData>
), thanks to using Partial<TVariables>
instead of K extends typeof TVariables
and Pick<TVariables, K>
.
@ArnaudBarre in #7476
Pass variables
and context
to a mutation's update
function. Note: The type of the update
function is now named MutationUpdaterFunction
rather than MutationUpdaterFn
, since the older type was broken beyond repair. If you are using MutationUpdaterFn
in your own code, please use MutationUpdaterFunction
instead.
@jcreighton in #7902
A resultCacheMaxSize
option may be passed to the InMemoryCache
constructor to limit the number of result objects that will be retained in memory (to speed up repeated reads), and calling cache.reset()
now releases all such memory.
@SofianHn in #8107
Fully remove result cache entries from LRU dependency system when the corresponding entities are removed from InMemoryCache
by eviction, or by any other means.
@sofianhn and @benjamn in #8147
Expose missing field errors in results.
@brainkim in #8262
Add expected/received variables
to No more mocked responses...
error messages generated by MockLink
.
@markneub in #8340
The InMemoryCache
version of the cache.gc
method now supports additional options for removing non-essential (recomputable) result caching data.
@benjamn in #8421
Suppress noisy Missing cache result fields...
warnings by default unless setLogVerbosity("debug")
called.
@benjamn in #8489
Improve interaction between React hooks and React Fast Refresh in development.
@andreialecu in #7952
The InMemoryCache
constructor should now be imported directly from @apollo/client
, rather than from a separate package. The apollo-cache-inmemory
package is no longer supported.
The @apollo/client/cache
entry point can be used to import InMemoryCache
without importing other parts of the Apollo Client codebase.
> @hwillson in #5577
[BREAKING] FragmentMatcher
, HeuristicFragmentMatcher
, and IntrospectionFragmentMatcher
have all been removed. We now recommend using InMemoryCache
’s possibleTypes
option instead. For more information see the Defining possibleTypes
manually section of the docs.
@benjamn in #5073
[BREAKING] As promised in the Apollo Client 2.6 blog post, all cache results are now frozen/immutable.
@benjamn in #5153
[BREAKING] Eliminate "generated" cache IDs to avoid normalizing objects with no meaningful ID, significantly reducing cache memory usage. This might be a backwards-incompatible change if your code depends on the precise internal representation of normalized data in the cache.
@benjamn in #5146
[BREAKING] InMemoryCache
will no longer merge the fields of written objects unless the objects are known to have the same identity, and the values of fields with the same name will not be recursively merged unless a custom merge
function is defined by a field policy for that field, within a type policy associated with the __typename
of the parent object.
@benjamn in #5603
[BREAKING] InMemoryCache
now throws when data with missing or undefined query fields is written into the cache, rather than just warning in development.
@benjamn in #6055
[BREAKING] client|cache.writeData
have been fully removed. writeData
usage is one of the easiest ways to turn faulty assumptions about how the cache represents data internally, into cache inconsistency and corruption. client|cache.writeQuery
, client|cache.writeFragment
, and/or cache.modify
can be used to update the cache.
@benjamn in #5923
InMemoryCache
now supports tracing garbage collection and eviction. Note that the signature of the evict
method has been simplified in a potentially backwards-incompatible way.
@benjamn in #5310
[beta-BREAKING] Please note that the cache.evict
method now requires Cache.EvictOptions
, though it previously supported positional arguments as well.
@danReynolds in #6141
@benjamn in #6364
Removing an entity object using the cache.evict
method does not automatically remove dangling references to that entity elsewhere in the cache, but dangling references will be automatically filtered from lists whenever those lists are read from the cache. You can define a custom field read
function to customize this behavior. See #6412, #6425, and #6454 for further explanation.
Cache methods that would normally trigger a broadcast, like cache.evict
, cache.writeQuery
, and cache.writeFragment
, can now be called with a named options object, which supports a broadcast: boolean
property that can be used to silence the broadcast, for situations where you want to update the cache multiple times without triggering a broadcast each time.
@benjamn in #6288
InMemoryCache
now console.warn
s in development whenever non-normalized data is dangerously overwritten, with helpful links to documentation about normalization and custom merge
functions.
@benjamn in #6372
The result caching system (introduced in #3394) now tracks dependencies at the field level, rather than at the level of whole entity objects, allowing the cache to return identical (===
) results much more often than before.
@benjamn in #5617
InMemoryCache
now has a method called modify
which can be used to update the value of a specific field within a specific entity object:
cache.modify({
id: cache.identify(post),
fields: {
comments(comments: Reference[], { readField }) {
return comments.filter(
(comment) => idToRemove !== readField("id", comment)
);
},
},
});
This API gracefully handles cases where multiple field values are associated with a single field name, and also removes the need for updating the cache by reading a query or fragment, modifying the result, and writing the modified result back into the cache. Behind the scenes, the cache.evict
method is now implemented in terms of cache.modify
.
@benjamn in #5909
and #6178
InMemoryCache
provides a new API for storing client state that can be updated from anywhere:
import { makeVar } from "@apollo/client";
const v = makeVar(123);
console.log(v());
console.log(v(v() + 1));
console.log(v());
v("asdf");
These variables are reactive in the sense that updating their values invalidates any previously cached query results that depended on the old values.
@benjamn in
#5799,
#5976, and
#6512
Various cache read and write performance optimizations, cutting read and write times by more than 50% in larger benchmarks.
@benjamn in #5948
The cache.readQuery
and cache.writeQuery
methods now accept an options.id
string, which eliminates most use cases for cache.readFragment
and cache.writeFragment
, and skips the implicit conversion of fragment documents to query documents performed by cache.{read,write}Fragment
.
@benjamn in #5930
Support cache.identify(entity)
for easily computing entity ID strings.
@benjamn in #5642
Support eviction of specific entity fields using cache.evict(id, fieldName)
.
@benjamn in #5643
Make InMemoryCache#evict
remove data from all EntityStore
layers.
@benjamn in #5773
Stop paying attention to previousResult
in InMemoryCache
.
@benjamn in #5644
Improve optimistic update performance by limiting cache key diversity.
@benjamn in #5648
Custom field read
functions can read from neighboring fields using the readField(fieldName)
helper, and may also read fields from other entities by calling readField(fieldName, objectOrReference)
.
@benjamn in #5651
Expose cache modify
and identify
to the mutate update
function.
@hwillson in #5956
Add a default gc
implementation to ApolloCache
.
@justinwaite in #5974