Docs
Launch GraphOS Studio

Refetching queries in Apollo Client


allows you to make local modifications to your data by updating the cache, but sometimes it's more straightforward to update your client-side data by refetching queries from the server.

In theory, you could refetch every active after a client-side update, but you can save time and network bandwidth by refetching queries more selectively. The InMemoryCache helps you determine which active queries might have been invalidated by recent cache updates.

Local cache updates and refetching work especially well in combination: your application can display the results of local cache modifications immediately, while also refetching in the background to obtain the very latest data from the server. The UI is then rerendered only if there are differences between local data and refetched data.

Refetching is especially common after a , so mutate functions accept options like refetchQueries and onQueryUpdated to specify which queries should be refetched, and how.

To selectively refetch queries outside of a , you instead use the refetchQueries method of ApolloClient, which is here.

client.refetchQueries

This method is new in 3.4.

Refetch options

The client.refetchQueries method take an options object that conforms to the following TypeScript interface:

interface RefetchQueriesOptions<
TCache extends ApolloCache<any>,
TResult = Promise<ApolloQueryResult<any>>,
> {
updateCache?: (cache: TCache) => void;
include?: Array<string | DocumentNode> | "all" | "active";
onQueryUpdated?: (
observableQuery: ObservableQuery<any>,
diff: Cache.DiffResult<any>,
lastDiff: Cache.DiffResult<any> | undefined,
) => boolean | TResult;
optimistic?: boolean;
}

These are described below:

Name /
Type
Description
updateCache

(cache: TCache) => void

Optional function that updates cached to trigger refetches of queries that include those fields.

include

Array<string | DocumentNode> | "all" | "active"

Optional array specifying queries to refetch. Each element can be either a 's string name or a DocumentNode object.

Analogous to the options.refetchQueries array for .

Pass "active" (or "all") as a shorthand to refetch all (active) queries.

onQueryUpdated

(observableQuery: ObservableQuery<any>, diff: Cache.DiffResult<any>, lastDiff: Cache.DiffResult<any> | undefined) => boolean | TResult

Optional callback function that's called once for each ObservableQuery that's either affected by options.updateCache or listed in options.include (or both).

If onQueryUpdated is not provided, the default implementation returns the result of calling observableQuery.refetch(). When onQueryUpdated is provided, it can dynamically decide whether (and how) each should be refetched.

Returning false from onQueryUpdated prevents the associated from being refetched.

optimistic

boolean

If true, the options.updateCache function is executed on a temporary optimistic layer of InMemoryCache, so its modifications can be discarded from the cache after observing which it invalidated.

Defaults to false, meaning options.updateCache updates the cache in a lasting way.

Refetch results

The client.refetchQueries method collects the TResult results returned by onQueryUpdated, defaulting to TResult = Promise<ApolloQueryResult<any>> if onQueryUpdated is not provided. It combines those results into a single Promise<TResolved[]> using Promise.all(results).

Thanks to the Promise-unwrapping behavior of Promise.all, this TResolved type is often the same type as TResult, except when TResult is a PromiseLike<TResolved> or a boolean.

The returned Promise object has two other useful properties:

Name /
Type
Description
queries

ObservableQuery[]

An array of ObservableQuery objects that were refetched.

results

TResult[]

An array of results that were either returned by onQueryUpdated, or provided by default in the absence of onQueryUpdated, including pending promises.

If onQueryUpdated returns false for a given , no result is provided for that query.

If onQueryUpdated returns true, the resulting Promise<ApolloQueryResult<any>> is included in the results array instead of true.

These two arrays parallel each other: they have the same length, and results[i] is the result produced by onQueryUpdated when called with the ObservableQuery found at queries[i], for any index i.

Refetch recipes

Refetching a specific query

To refetch a specific by name, use the include option by itself:

await client.refetchQueries({
include: ["SomeQueryName"],
});

The include option can also refetch a specific using its DocumentNode:

await client.refetchQueries({
include: [SOME_QUERY],
});

Refetching all queries

To refetch all active queries, pass the "active" shorthand for include:

await client.refetchQueries({
include: "active",
});

To refetch all queries managed by (even those with no observers, or whose components are currently unmounted), pass "all" for include:

await client.refetchQueries({
include: "all", // Consider using "active" instead!
});

Refetching queries affected by cache updates

You can refetch queries affected by cache updates performed in the updateCache callback:

await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
});

This refetches any queries that depend on Query.someRootField, without requiring you to know in advance which queries might be included. Any combination of cache (writeQuery, writeFragment, modify, evict, etc.) is allowed within updateCache.

Updates performed by updateCache persist in the cache by default. You can perform them in a temporary optimistic layer instead, if you want them to be discarded immediately after client.refetchQueries is done observing them, leaving the cache unchanged:

await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
// Evict Query.someRootField only temporarily, in an optimistic layer.
optimistic: true,
});

Another way to "update" the cache without actually changing cache data is to use cache.modify and its INVALIDATE sentinel object:

await client.refetchQueries({
updateCache(cache) {
cache.modify({
fields: {
someRootField(value, { INVALIDATE }) {
// Update queries that involve Query.someRootField, without actually
// changing its value in the cache.
return INVALIDATE;
},
},
});
},
});

Before client.refetchQueries was introduced, the INVALIDATE sentinel was not very useful, because invalidated queries with fetchPolicy: "cache-first" would typically re-read unchanged results, and therefore decide not to perform a network request. The client.refetchQueries method makes this invalidation system more accessible to application code, so you can control the refetching behavior of invalidated queries.

In all of the examples above, whether we use include or updateCache, client.refetchQueries refetches affected queries from the network and includes the resulting Promise<ApolloQueryResult<any>> results in the Promise<TResolved[]> returned by client.refetchQueries.

If a particular is included both by include and by updateCache, that is refetched only once. In other words, the include option is a good way to make sure certain queries are always included, no matter which queries are included by updateCache.

Refetching selectively

In development, you probably want to make sure the appropriate queries are getting refetched, rather than blindly refetching them. To intercept each before refetching, you can specify an onQueryUpdated callback:

const results = await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
onQueryUpdated(observableQuery) {
// Logging and/or debugger breakpoints can be useful in development to
// understand what client.refetchQueries is doing.
console.log(`Examining ObservableQuery ${observableQuery.queryName}`);
debugger;
// Proceed with the default refetching behavior, as if onQueryUpdated
// was not provided.
return true;
},
});
results.forEach(result => {
// These results will be ApolloQueryResult<any> objects, after all
// results have been refetched from the network.
});

Notice how adding onQueryUpdated in this example did not change the refetching behavior of client.refetchQueries, allowing us to use onQueryUpdated purely for diagnostic or debugging purposes.

If you want to skip certain queries that would otherwise be included, return false from onQueryUpdated:

await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
onQueryUpdated(observableQuery, { complete, result, missing }) {
console.log(`Examining ObservableQuery ${
observableQuery.queryName
} whose latest result is ${JSON.stringify(result)} which is ${
complete ? "complete" : "incomplete"
}`);
if (shouldIgnoreQuery(observableQuery)) {
return false;
}
// Refetch the query unconditionally from the network.
return true;
},
});

In case the ObservableQuery does not provide enough information, you can also examine the latest result for the , along with information about its completeness and missing , using the Cache.DiffResult object passed as the second parameter to onQueryUpdated:

await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
onQueryUpdated(observableQuery, { complete, result, missing }) {
if (shouldIgnoreQuery(observableQuery)) {
return false;
}
if (complete) {
// Update the query according to its chosen FetchPolicy, rather than
// refetching it unconditionally from the network.
return observableQuery.reobserve();
}
// Refetch the query unconditionally from the network.
return true;
},
});

Because onQueryUpdated has the ability to filter queries dynamically, it also pairs well with the bulk include options mentioned above:

await client.refetchQueries({
// Include all active queries by default, which may be ill-advised unless
// you also use onQueryUpdated to filter those queries.
include: "active";
// Called once for every active query, allowing dynamic filtering:
onQueryUpdated(observableQuery) {
return !shouldIngoreQuery(observableQuery);
},
});

Handling refetch errors

In the examples above, we await client.refetchQueries(...) to find out the final ApolloQueryResult<any> results for all the refetched queries. This combined promise is created with Promise.all, so a single failure rejects the entire Promise<TResolved[]>, potentially hiding other successful results. If this is a problem, you can use the queries and results arrays returned by client.refetchQueries instead of (or in addition to) awaiting the Promise:

const { queries, results } = client.refetchQueries({
// Specific client.refetchQueries options are not relevant to this example.
});
const finalResults = await Promise.all(
results.map((result, i) => {
return Promise.resolve(result).catch(error => {
console.error(`Error refetching query ${queries[i].queryName}: ${error}`);
return null; // Silence this Promise rejection.
});
})
});

In the future, just as additional input options may be added to the client.refetchQueries method, additional properties may be added to its result object, supplementing its Promise-related properties and the queries and results arrays.

If you discover that some specific additional client.refetchQueries input options or result properties would be useful, please feel free to open an issue or start a discussion explaining your use case(s).

Corresponding client.mutate options

For refetching after a , client.mutate supports options similar to client.refetchQueries, which you should use instead of client.refetchQueries, because it's important for refetching logic to happen at specific times during the process.

For historical reasons, client.mutate options have slightly different names from the new client.refetchQueries options, but their internal implementation is substantially the same, so you can translate between them using the following table:

client.mutate(options)client.refetchQueries(options)
options.refetchQueriesoptions.include
options.updateoptions.updateCache
options.onQueryUpdatedoptions.onQueryUpdated
options.awaitRefetchQueriesReturn a Promise from onQueryUpdated
Previous
Mutations
Next
Subscriptions
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company