Skip to content

Commit

Permalink
Merge pull request #7827 from apollographql/mutation-reobserveQuery-u…
Browse files Browse the repository at this point in the history
…sing-batch

Support options.reobserveQuery callback for mutations.
  • Loading branch information
benjamn authored Mar 26, 2021
2 parents c3d410f + 892375c commit fb0dc25
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 36 deletions.
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>({
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

0 comments on commit fb0dc25

Please sign in to comment.