Skip to content

Commit

Permalink
Merge pull request #3169 from jzimmek/master
Browse files Browse the repository at this point in the history
Add `awaitRefetchQueries` option to be able to await refetched queries before resolving mutation
  • Loading branch information
hwillson authored Jul 23, 2018
2 parents 78838f3 + 5e9a6ce commit 02e6cd9
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
[@ivank](https://github.com/ivank) in [#3598](https://github.com/apollographql/apollo-client/pull/3598)
- Add optional generic type params for variables on low level methods. <br/>
[@mvestergaard](https://github.com/mvestergaard) in [#3588](https://github.com/apollographql/apollo-client/pull/3588)
- Add a new `awaitRefetchQueries` config option to the Apollo Client
`mutate` function, that when set to `true` will wait for all
`refetchQueries` to be fully refetched, before resolving the mutation
call. `awaitRefetchQueries` is `false` by default. <br/>
[@jzimmek](https://github.com/jzimmek) in [#](https://github.com/apollographql/apollo-client/pull/3169)

### Apollo Boost (vNext)

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
{
"name": "apollo-client",
"path": "./packages/apollo-client/lib/bundle.min.js",
"maxSize": "11.8 kB"
"maxSize": "12.5 kB"
},
{
"name": "apollo-boost",
"path": "./packages/apollo-boost/lib/bundle.min.js",
"maxSize": "35 kB"
"maxSize": "36 kB"
},
{
"name": "apollo-utilities",
Expand Down
111 changes: 64 additions & 47 deletions packages/apollo-client/src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class QueryManager<TStore> {
optimisticResponse,
updateQueries: updateQueriesByName,
refetchQueries = [],
awaitRefetchQueries = false,
update: updateWithProxyFn,
errorPolicy = 'none',
fetchPolicy,
Expand Down Expand Up @@ -192,6 +193,67 @@ export class QueryManager<TStore> {
...context,
optimisticResponse,
});

const completeMutation = async () => {
if (error) {
this.mutationStore.markMutationError(mutationId, error);
}

this.dataStore.markMutationComplete({
mutationId,
optimisticResponse,
});

this.broadcastQueries();

if (error) {
throw error;
}

// allow for conditional refetches
// XXX do we want to make this the only API one day?
if (typeof refetchQueries === 'function') {
refetchQueries = refetchQueries(storeResult as ExecutionResult);
}

const refetchQueryPromises: Promise<
ApolloQueryResult<any>[] | ApolloQueryResult<{}>
>[] = [];

for (const refetchQuery of refetchQueries) {
if (typeof refetchQuery === 'string') {
const promise = this.refetchQueryByName(refetchQuery);
if (promise) {
refetchQueryPromises.push(promise);
}
continue;
}

refetchQueryPromises.push(
this.query({
query: refetchQuery.query,
variables: refetchQuery.variables,
fetchPolicy: 'network-only',
}),
);
}

if (awaitRefetchQueries) {
await Promise.all(refetchQueryPromises);
}

this.setQuery(mutationId, () => ({ document: undefined }));
if (
errorPolicy === 'ignore' &&
storeResult &&
graphQLResultHasError(storeResult)
) {
delete storeResult.errors;
}

return storeResult as FetchResult<T>;
};

execute(this.link, operation).subscribe({
next: (result: ExecutionResult) => {
if (graphQLResultHasError(result) && errorPolicy === 'none') {
Expand All @@ -215,6 +277,7 @@ export class QueryManager<TStore> {
}
storeResult = result as FetchResult<T>;
},

error: (err: Error) => {
this.mutationStore.markMutationError(mutationId, err);
this.dataStore.markMutationComplete({
Expand All @@ -230,54 +293,8 @@ export class QueryManager<TStore> {
}),
);
},
complete: () => {
if (error) {
this.mutationStore.markMutationError(mutationId, error);
}

this.dataStore.markMutationComplete({
mutationId,
optimisticResponse,
});

this.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') {
refetchQueries = refetchQueries(storeResult as ExecutionResult);
}

if (refetchQueries) {
refetchQueries.forEach(refetchQuery => {
if (typeof refetchQuery === 'string') {
this.refetchQueryByName(refetchQuery);
return;
}

this.query({
query: refetchQuery.query,
variables: refetchQuery.variables,
fetchPolicy: 'network-only',
});
});
}

this.setQuery(mutationId, () => ({ document: undefined }));
if (
errorPolicy === 'ignore' &&
storeResult &&
graphQLResultHasError(storeResult)
) {
delete storeResult.errors;
}
resolve(storeResult as FetchResult<T>);
},
complete: () => completeMutation().then(resolve, reject),
});
});
}
Expand Down
114 changes: 114 additions & 0 deletions packages/apollo-client/src/core/__tests__/QueryManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3933,6 +3933,7 @@ describe('QueryManager', () => {
});
});
});

describe('refetchQueries', () => {
const oldWarn = console.warn;
let warned: any;
Expand Down Expand Up @@ -4447,6 +4448,119 @@ describe('QueryManager', () => {
done();
});
});

describe('awaitRefetchQueries', () => {
function awaitRefetchTest({ awaitRefetchQueries }) {
const query = gql`
query getAuthors($id: ID!) {
author(id: $id) {
firstName
lastName
}
}
`;

const queryData = {
author: {
firstName: 'John',
lastName: 'Smith',
},
};

const mutation = gql`
mutation changeAuthorName {
changeAuthorName(newName: "Jack Smith") {
firstName
lastName
}
}
`;

const mutationData = {
changeAuthorName: {
firstName: 'Jack',
lastName: 'Smith',
},
};

const secondReqData = {
author: {
firstName: 'Jane',
lastName: 'Johnson',
},
};

const variables = { id: '1234' };

const queryManager = mockQueryManager(
{
request: { query, variables },
result: { data: queryData },
},
{
request: { query: mutation },
result: { data: mutationData },
},
{
request: { query, variables },
result: { data: secondReqData },
},
);

const observable = queryManager.watchQuery<any>({
query,
variables,
notifyOnNetworkStatusChange: false,
});

let mutationComplete = false;
return observableToPromise(
{ observable },
result => {
expect(stripSymbols(result.data)).toEqual(queryData);
const mutateOptions = {
mutation,
refetchQueries: ['getAuthors'],
};
if (awaitRefetchQueries) {
mutateOptions.awaitRefetchQueries = awaitRefetchQueries;
}
queryManager.mutate(mutateOptions).then(() => {
mutationComplete = true;
});
},
result => {
if (awaitRefetchQueries) {
expect(mutationComplete).not.toBeTruthy();
} else {
expect(mutationComplete).toBeTruthy();
}
expect(stripSymbols(observable.currentResult().data)).toEqual(
secondReqData,
);
expect(stripSymbols(result.data)).toEqual(secondReqData);
},
);
}

it(
'should not wait for `refetchQueries` to complete before resolving ' +
'the mutation, when `awaitRefetchQueries` is falsy',
() => {
awaitRefetchTest({ awaitRefetchQueries: undefined });
awaitRefetchTest({ awaitRefetchQueries: false });
},
);

it(
'should wait for `refetchQueries` to complete before resolving ' +
'the mutation, when `awaitRefetchQueries` is `true`',
() => {
awaitRefetchTest({ awaitRefetchQueries: true });
},
);
});

describe('store watchers', () => {
it('does not fill up the store on resolved queries', () => {
const query1 = gql`
Expand Down
10 changes: 10 additions & 0 deletions packages/apollo-client/src/core/watchQueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ export interface MutationBaseOptions<
| ((result: ExecutionResult) => RefetchQueryDescription)
| RefetchQueryDescription;

/**
* By default, `refetchQueries` does not wait for the refetched queries to
* be completed, before resolving the mutation `Promise`. This ensures that
* query refetching does not hold up mutation response handling (query
* refetching is handled asynchronously). Set `awaitRefetchQueries` to
* `true` if you would like to wait for the refetched queries to complete,
* before the mutation can be marked as resolved.
*/
awaitRefetchQueries?: boolean;

/**
* A function which provides a {@link DataProxy} and the result of the
* mutation to allow the user to update the store based on the results of the
Expand Down

0 comments on commit 02e6cd9

Please sign in to comment.