Skip to content

Commit

Permalink
Feature: support client.refetchQueries for refetching queries imperat…
Browse files Browse the repository at this point in the history
…ively (#7431)
  • Loading branch information
dannycochran committed Dec 11, 2020
1 parent 564708e commit bc7e533
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/source/api/core/ApolloClient.mdx
Expand Up @@ -101,6 +101,7 @@ different value for the same option in individual function calls.
<TypescriptApiBox name="ApolloClient.onClearStore" />
<TypescriptApiBox name="ApolloClient.stop" />
<TypescriptApiBox name="ApolloClient.reFetchObservableQueries" />
<TypescriptApiBox name="ApolloClient.refetchQueries" />

## Types

Expand Down
13 changes: 13 additions & 0 deletions src/__tests__/client.ts
Expand Up @@ -2426,6 +2426,19 @@ describe('client', () => {
expect(spy).toHaveBeenCalled();
});

it('has a refetchQueries method which calls QueryManager', async () => {
// TODO(dannycochran)
const client = new ApolloClient({
link: ApolloLink.empty(),
cache: new InMemoryCache(),
});

// @ts-ignore
const spy = jest.spyOn(client.queryManager, 'refetchQueries');
await client.refetchQueries(['Author1']);
expect(spy).toHaveBeenCalled();
});

itAsync('should propagate errors from network interface to observers', (resolve, reject) => {
const link = ApolloLink.from([
() =>
Expand Down
19 changes: 19 additions & 0 deletions src/core/ApolloClient.ts
Expand Up @@ -22,6 +22,7 @@ import {
MutationOptions,
SubscriptionOptions,
WatchQueryFetchPolicy,
RefetchQueryDescription,
} from './watchQueryOptions';

import {
Expand Down Expand Up @@ -516,6 +517,24 @@ export class ApolloClient<TCacheShape> implements DataProxy {
return this.queryManager.reFetchObservableQueries(includeStandby);
}

/**
* Refetches specified active queries. Similar to "reFetchObservableQueries()" but with a specific list of queries.
*
* `refetchQueries()` is useful for use cases to imperatively refresh a selection of queries.
*
* It is important to remember that `refetchQueries()` *will* refetch specified active
* queries. This means that any components that might be mounted will execute
* their queries again using your network interface. If you do not want to
* re-execute any queries then you should make sure to stop watching any
* active queries.
* Takes optional parameter `includeStandby` which will include queries in standby-mode when refetching.
*/
public refetchQueries(
queries: RefetchQueryDescription,
): Promise<ApolloQueryResult<any>[]> {
return Promise.all(this.queryManager.refetchQueries(queries));
}

/**
* Exposes the cache's complete state, in a serializable format for later restoration.
*/
Expand Down
63 changes: 35 additions & 28 deletions src/core/QueryManager.ts
Expand Up @@ -28,6 +28,7 @@ import {
MutationOptions,
WatchQueryFetchPolicy,
ErrorPolicy,
RefetchQueryDescription,
} from './watchQueryOptions';
import { ObservableQuery } from './ObservableQuery';
import { NetworkStatus, isNetworkRequestInFlight } from './networkStatus';
Expand Down Expand Up @@ -263,34 +264,7 @@ export class QueryManager<TStore> {
refetchQueries = refetchQueries(storeResult!);
}

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

if (isNonEmptyArray(refetchQueries)) {
refetchQueries.forEach(refetchQuery => {
if (typeof refetchQuery === 'string') {
self.queries.forEach(({ observableQuery }) => {
if (observableQuery &&
observableQuery.queryName === refetchQuery) {
refetchQueryPromises.push(observableQuery.refetch());
}
});
} else {
const queryOptions: QueryOptions = {
query: refetchQuery.query,
variables: refetchQuery.variables,
fetchPolicy: 'network-only',
};

if (refetchQuery.context) {
queryOptions.context = refetchQuery.context;
}

refetchQueryPromises.push(self.query(queryOptions));
}
});
}
const refetchQueryPromises = self.refetchQueries(refetchQueries);

Promise.all(
awaitRefetchQueries ? refetchQueryPromises : [],
Expand Down Expand Up @@ -1023,6 +997,39 @@ export class QueryManager<TStore> {
return concast;
}

public refetchQueries(queries: RefetchQueryDescription):
Promise<ApolloQueryResult<any>>[] {
const self = this;
const refetchQueryPromises: Promise<ApolloQueryResult<any>>[] = [];

if (isNonEmptyArray(queries)) {
queries.forEach(refetchQuery => {
if (typeof refetchQuery === 'string') {
self.queries.forEach(({ observableQuery }) => {
if (observableQuery &&
observableQuery.queryName === refetchQuery) {
refetchQueryPromises.push(observableQuery.refetch());
}
});
} else {
const queryOptions: QueryOptions = {
query: refetchQuery.query,
variables: refetchQuery.variables,
fetchPolicy: 'network-only',
};

if (refetchQuery.context) {
queryOptions.context = refetchQuery.context;
}

refetchQueryPromises.push(self.query(queryOptions));
}
});
}

return refetchQueryPromises;
}

private fetchQueryByPolicy<TData, TVars>(
queryInfo: QueryInfo,
options: WatchQueryOptions<TVars, TData>,
Expand Down
91 changes: 91 additions & 0 deletions src/core/__tests__/QueryManager/index.ts
Expand Up @@ -4243,6 +4243,97 @@ describe('QueryManager', () => {
});
});

describe('refetching specified queries', () => {
itAsync('returns a promise resolving when all queries have been refetched', (resolve, reject) => {
const query = gql`
query GetAuthor {
author {
firstName
lastName
}
}
`;

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

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

const query2 = gql`
query GetAuthor2 {
author2 {
firstName
lastName
}
}
`;

const data2 = {
author2: {
firstName: 'John',
lastName: 'Smith',
},
};

const data2Changed = {
author2: {
firstName: 'John changed',
lastName: 'Smith',
},
};

const queryManager = createQueryManager({
link: mockSingleLink({
request: { query },
result: { data },
}, {
request: { query: query2 },
result: { data: data2 },
}, {
request: { query },
result: { data: dataChanged },
}, {
request: { query: query2 },
result: { data: data2Changed },
}).setOnError(reject),
});

const observable = queryManager.watchQuery<any>({ query });
const observable2 = queryManager.watchQuery<any>({ query: query2 });

return Promise.all([
observableToPromise({ observable }, result =>
expect(stripSymbols(result.data)).toEqual(data),
),
observableToPromise({ observable: observable2 }, result =>
expect(stripSymbols(result.data)).toEqual(data2),
),
]).then(() => {
observable.subscribe({ next: () => null });
observable2.subscribe({ next: () => null });

return Promise.all(queryManager.refetchQueries(['GetAuthor', 'GetAuthor2'])).then(() => {
const result = getCurrentQueryResult(observable);
expect(result.partial).toBe(false);
expect(stripSymbols(result.data)).toEqual(dataChanged);

const result2 = getCurrentQueryResult(observable2);
expect(result2.partial).toBe(false);
expect(stripSymbols(result2.data)).toEqual(data2Changed);
});
}).then(resolve, reject);
});
});

describe('loading state', () => {
itAsync('should be passed as false if we are not watching a query', (resolve, reject) => {
const query = gql`
Expand Down

0 comments on commit bc7e533

Please sign in to comment.