Refetching Queries
Although Apollo Client allows you to make local modifications to previously received GraphQL data by updating the cache, sometimes the easiest way to update your client-side GraphQL data is to refetch it from the server.
In principle, you could refetch every active query after any client-side update,
but you can save time and network bandwidth by refetching queries more
selectively, taking advantage of InMemoryCache
to determine which watched
queries may have been invalidated by recent cache updates.
These two approaches (local updates and refetching) work well together: if your application displays the results of local cache modifications immediately, you can use refetching in the background to obtain the very latest data from the server, rerendering the UI only if there are differences between local data and refetched data.
Refetching is especially common after a mutation, so client.mutate
accepts
options like refetchQueries
and onQueryUpdated
to specify which queries
should be refetched, and how. However, the tools of selective refetching are
available even if you are not performing a mutation, in the form of the
client.refetchQueries
method.
client.refetchQueries
Refetch options
Name / Type | Description |
---|---|
The
Descriptions of these fields can be found in the table below. | |
| Optional function that updates the cache as a way of triggering refetches of queries whose data were affected by those cache updates. |
| Optional array specifying the names or Analogous to the Pass |
| Optional callback function that will be called for each If |
| If Defaults to |
Refetch results
The client.refetchQueries
method collects the TResult
results returned by
onQueryUpdated
, defaulting to TResult = Promise<ApolloQueryResult<any>>
if
onQueryUpdated
is not provided, and combines the results into a single
Promise<TResolved[]>
using Promise.all(results)
.
Thanks to the
Promise
-unwrapping behavior ofPromise.all
, thisTResolved
type will often be the same type asTResult
, except whenTResult
is aPromiseLike<TResolved>
or aboolean
.
The returned Promise
object has two other useful properties:
Property | 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 query, no result will be provided for that query. If onQueryUpdated returns true , the resulting Promise<ApolloQueryResult<any>> will be included in the results array, rather than 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
To refetch a specific query by name, use the include
option by itself:
await client.refetchQueries({
include: ["SomeQueryName"],
});
The include
option can also refetch a specific query using its DocumentNode
:
await client.refetchQueries({
include: [SOME_QUERY],
});
To refetch all active queries, pass the "active"
shorthand for include
:
await client.refetchQueries({
include: "active",
});
To refetch all queries managed by Apollo Client, even those with no observers,
or whose components are currently unmounted, pass "all"
for include
:
await client.refetchQueries({
include: "all", // Consider using "active" instead!
});
Alternatively, you can refetch queries affected by cache updates performed in
the updateCache
callback:
await client.refetchQueries({
updateCache(cache) {
cache.evict({ fieldName: "someRootField" });
},
});
This will refetch any queries that depend on Query.someRootField
, without
requiring you to know in advance which queries might be included. Any
combination of cache operations (writeQuery
, writeFragment
, modify
,
evict
, etc.) is allowed within updateCache
. Updates performed by
updateCache
persist in the cache by default, but you can choose to 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 of updating the cache without 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, theINVALIDATE
sentinel was not very useful, since invalidated queries withfetchPolicy: "cache-first"
would typically re-read unchanged results, and therefore decide not to perform a network request. Theclient.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
will refetch the affected queries from the network and
include the resulting Promise<ApolloQueryResult<any>>
results in the
Promise<TResolved[]>
returned by client.refetchQueries
. If a single query
happens to be included both by include
and by updateCache
, that query will
be 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
.
In development, you will probably want to make sure the appropriate queries are
getting refetched, rather than blindly refetching them. To intercept each query
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) {
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 query, along with information about its
complete
ness and missing
fields, 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);
},
});
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 will reject
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) await
ing 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).
Relationship to client.mutate
options
For refetching after a mutation, the client.mutate
method 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 mutation process.
For historical reasons, the 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.refetchQueries | ⇔ | options.include |
options.update | ⇔ | options.updateCache |
options.onQueryUpdated | ⇔ | options.onQueryUpdated |
options.awaitRefetchQueries | ⇔ | just return a Promise from onQueryUpdated |