Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support options.reobserveQuery callback for mutations. #7827

Merged
merged 8 commits into from
Mar 26, 2021
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ TBD
```
[@benjamn](https://github.com/benjamn) in [#7810](https://github.com/apollographql/apollo-client/pull/7810)

- Mutations now accept an optional callback function called `reobserveQuery`, 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 `reobserveQuery`, 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 `reobserveQuery`, the mutation will automatically await that `Promise`, rendering the `awaitRefetchQueries` option unnecessary. <br/>
[@benjamn](https://github.com/benjamn) in [#7827](https://github.com/apollographql/apollo-client/pull/7827)

- Support `client.refetchQueries` as an imperative way to refetch queries, without having to pass `options.refetchQueries` to `client.mutate`. <br/>
[@dannycochran](https://github.com/dannycochran) in [#7431](https://github.com/apollographql/apollo-client/pull/7431)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
{
"name": "apollo-client",
"path": "./dist/apollo-client.cjs.min.js",
"maxSize": "26.4 kB"
"maxSize": "26.5 kB"
}
],
"peerDependencies": {
Expand Down
5 changes: 4 additions & 1 deletion src/cache/core/types/Cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export namespace Cache {
// declaring the returnPartialData option.
}

export interface WatchOptions extends ReadOptions {
export interface WatchOptions<
Watcher extends object = Record<string, any>
> extends ReadOptions {
watcher?: Watcher;
immediate?: boolean;
callback: WatchCallback;
lastDiff?: DiffResult<any>;
Expand Down
3 changes: 2 additions & 1 deletion src/core/QueryInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export class QueryInfo {
// updateWatch method.
private cancel() {}

private lastWatch?: Cache.WatchOptions;
private lastWatch?: Cache.WatchOptions<QueryInfo>;

private updateWatch(variables = this.variables) {
const oq = this.observableQuery;
Expand All @@ -276,6 +276,7 @@ export class QueryInfo {
query: this.document!,
variables,
optimistic: true,
watcher: this,
callback: diff => this.setDiff(diff),
});
}
Expand Down
81 changes: 51 additions & 30 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { NetworkStatus, isNetworkRequestInFlight } from './networkStatus';
import {
ApolloQueryResult,
OperationVariables,
ReobserveQueryCallback,
} from './types';
import { LocalState } from './LocalState';

Expand Down Expand Up @@ -135,6 +136,7 @@ export class QueryManager<TStore> {
refetchQueries = [],
awaitRefetchQueries = false,
update: updateWithProxyFn,
reobserveQuery,
errorPolicy = 'none',
fetchPolicy,
context = {},
Expand Down Expand Up @@ -184,85 +186,85 @@ export class QueryManager<TStore> {

return new Promise((resolve, reject) => {
let storeResult: FetchResult<T> | null;
let error: ApolloError;

self.getObservableFromLink(
mutation,
{
...context,
optimisticResponse,
},
variables,
false,
).subscribe({
next(result: FetchResult<T>) {
return asyncMap(
self.getObservableFromLink(
mutation,
{
...context,
optimisticResponse,
},
variables,
false,
),

(result: FetchResult<T>) => {
if (graphQLResultHasError(result) && errorPolicy === 'none') {
error = new ApolloError({
throw new ApolloError({
graphQLErrors: result.errors,
});
return;
}

if (mutationStoreValue) {
mutationStoreValue.loading = false;
mutationStoreValue.error = null;
}

storeResult = result;

if (fetchPolicy !== 'no-cache') {
try {
self.markMutationResult<T>({
// Returning the result of markMutationResult here makes the
// mutation await any Promise that markMutationResult returns,
// since we are returning this Promise from the asyncMap mapping
// function.
return self.markMutationResult<T>({
benjamn marked this conversation as resolved.
Show resolved Hide resolved
mutationId,
result,
document: mutation,
variables,
errorPolicy,
updateQueries,
update: updateWithProxyFn,
reobserveQuery,
});
} catch (e) {
error = new ApolloError({
// Likewise, throwing an error from the asyncMap mapping function
// will result in calling the subscribed error handler function.
throw new ApolloError({
networkError: e,
});
return;
}
}

storeResult = result;
},

).subscribe({
error(err: Error) {
if (mutationStoreValue) {
mutationStoreValue.loading = false;
mutationStoreValue.error = err;
}

if (optimisticResponse) {
self.cache.removeOptimistic(mutationId);
}

self.broadcastQueries();

reject(
new ApolloError({
err instanceof ApolloError ? err : new ApolloError({
networkError: err,
}),
);
},

complete() {
if (error && mutationStoreValue) {
mutationStoreValue.loading = false;
mutationStoreValue.error = error;
}

if (optimisticResponse) {
self.cache.removeOptimistic(mutationId);
}

self.broadcastQueries();

if (error) {
reject(error);
return;
}

// allow for conditional refetches
// XXX do we want to make this the only API one day?
if (typeof refetchQueries === 'function') {
Expand Down Expand Up @@ -301,9 +303,10 @@ export class QueryManager<TStore> {
cache: ApolloCache<TStore>,
result: FetchResult<TData>,
) => void;
reobserveQuery?: ReobserveQueryCallback;
},
cache = this.cache,
) {
): Promise<void> {
if (shouldWriteResult(mutation.result, mutation.errorPolicy)) {
const cacheWrites: Cache.WriteOptions[] = [{
result: mutation.result.data,
Expand Down Expand Up @@ -351,6 +354,8 @@ export class QueryManager<TStore> {
});
}

const reobserveResults: any[] = [];

cache.batch({
transaction(c) {
cacheWrites.forEach(write => c.write(write));
Expand All @@ -362,10 +367,26 @@ export class QueryManager<TStore> {
update(c, mutation.result);
}
},

// Write the final mutation.result to the root layer of the cache.
optimistic: false,

onDirty: mutation.reobserveQuery && ((watch, diff) => {
if (watch.watcher instanceof QueryInfo) {
const oq = watch.watcher.observableQuery;
if (oq) {
reobserveResults.push(mutation.reobserveQuery!(oq, diff));
// Prevent the normal cache broadcast of this result.
return false;
}
}
}),
});

return Promise.all(reobserveResults).then(() => void 0);
}

return Promise.resolve();
}

public markMutationOptimistic<TData>(
Expand Down
Loading