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

await refetched queries before resolve mutation #3169

Merged
merged 12 commits into from
Jul 23, 2018
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