Refetching queries in Apollo Client
Apollo Client allows you to make local modifications to your GraphQL data by updating the cache, but sometimes it's more straightforward to update your client-side GraphQL data by refetching queries from the server.
In theory, you could refetch every active query 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 mutation, 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 mutation, you instead use the refetchQueries
method of ApolloClient
, which is documented here.
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.
Signature
1refetchQueries<TCache, TResult>(
2 options: ApolloClient.RefetchQueriesOptions<TCache, TResult>
3): ApolloClient.RefetchQueriesResult<TResult>
Parameters
Show/hide child attributes
RefetchQueriesInclude
Optional array specifying queries to refetch. Each element can be either a query's string name or a DocumentNode
object.
Pass "active"
as a shorthand to refetch all active queries, or "all"
to refetch all active and inactive queries.
Analogous to the options.refetchQueries
array for mutations.
OnQueryUpdated<TResult> | null
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 query should be refetched.
Returning false
from onQueryUpdated
prevents the associated query from being refetched.
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 fields it invalidated.
Defaults to false
, meaning options.updateCache
updates the cache in a lasting way.
(cache: TCache) => void
Optional function that updates cached fields to trigger refetches of queries that include those fields.
Result
Theclient.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)
.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
.Promise
object has two other useful properties:ObservableQuery<any>[]
An array of ObservableQuery objects corresponding 1:1 to TResult values
in the results arrays (both the result
property and the resolved value).
InternalRefetchQueriesResult<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 is provided for that query.
If onQueryUpdated
returns true
, the resulting Promise<ApolloQueryResult<any>>
is included in the results
array instead of true
.
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 query by name, use the include
option by itself:
1await client.refetchQueries({
2 include: ["SomeQueryName"],
3});
The include
option can also refetch a specific query using its DocumentNode
:
1await client.refetchQueries({
2 include: [SOME_QUERY],
3});
Refetching all queries
- Active queries: Have at least one subscriber and are not skipped or in
standby
- Inactive queries: Have a subscriber but are either skipped from a React hook or have a
fetchPolicy
ofstandby
To refetch all active queries, pass the "active"
shorthand for include
:
1await client.refetchQueries({
2 include: "active",
3});
Only queries with active subscribers are registered with the client. This means refetchQueries
will only affect queries that have at least one subscriber. Queries without subscribers are not tracked and cannot be refetched.
cache-only
queries are excluded from refetchQueries
in all situations, including when affected by updateCache
operations. A cache-only
query can be refetched however by providing an onQueryUpdated
function.
To refetch all queries managed by Apollo Client, including inactive queries, pass "all"
for for include
:
1await client.refetchQueries({
2 include: "all", // Consider using "active" instead!
3});
Refetching queries affected by cache updates
You can refetch queries affected by cache updates performed in the updateCache
callback:
1await client.refetchQueries({
2 updateCache(cache) {
3 cache.evict({ fieldName: "someRootField" });
4 },
5});
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 operations (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:
1await client.refetchQueries({
2 updateCache(cache) {
3 cache.evict({ fieldName: "someRootField" });
4 },
5
6 // Evict Query.someRootField only temporarily, in an optimistic layer.
7 optimistic: true,
8});
Another way to "update" the cache without actually changing cache data is to use cache.modify
and its INVALIDATE
sentinel object:
1await client.refetchQueries({
2 updateCache(cache) {
3 cache.modify({
4 fields: {
5 someRootField(value, { INVALIDATE }) {
6 // Update queries that involve Query.someRootField, without actually
7 // changing its value in the cache.
8 return INVALIDATE;
9 },
10 },
11 });
12 },
13});
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 query is included both by include
and by updateCache
, that query 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 query before refetching, you can specify an onQueryUpdated
callback:
1const results = await client.refetchQueries({
2 updateCache(cache) {
3 cache.evict({ fieldName: "someRootField" });
4 },
5
6 onQueryUpdated(observableQuery) {
7 // Logging and/or debugger breakpoints can be useful in development to
8 // understand what client.refetchQueries is doing.
9 console.log(`Examining ObservableQuery ${observableQuery.queryName}`);
10 debugger;
11
12 // Proceed with the default refetching behavior, as if onQueryUpdated
13 // was not provided.
14 return true;
15 },
16});
17
18results.forEach((result) => {
19 // These results will be ApolloQueryResult<any> objects, after all
20 // results have been refetched from the network.
21});
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
:
1await client.refetchQueries({
2 updateCache(cache) {
3 cache.evict({ fieldName: "someRootField" });
4 },
5
6 onQueryUpdated(observableQuery, { complete, result, missing }) {
7 console.log(
8 `Examining ObservableQuery ${
9 observableQuery.queryName
10 } whose latest result is ${JSON.stringify(result)} which is ${
11 complete ? "complete" : "incomplete"
12 }`
13 );
14
15 if (shouldIgnoreQuery(observableQuery)) {
16 return false;
17 }
18
19 // Refetch the query unconditionally from the network.
20 return true;
21 },
22});
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
:
1await client.refetchQueries({
2 updateCache(cache) {
3 cache.evict({ fieldName: "someRootField" });
4 },
5
6 onQueryUpdated(observableQuery, { complete, result, missing }) {
7 if (shouldIgnoreQuery(observableQuery)) {
8 return false;
9 }
10
11 if (complete) {
12 // Update the query according to its chosen FetchPolicy, rather than
13 // refetching it unconditionally from the network.
14 return observableQuery.reobserve();
15 }
16
17 // Refetch the query unconditionally from the network.
18 return true;
19 },
20});
Because onQueryUpdated
has the ability to filter queries dynamically, it also pairs well with the bulk include
options mentioned above:
1await client.refetchQueries({
2 // Include all active queries by default, which may be ill-advised unless
3 // you also use onQueryUpdated to filter those queries.
4 include: "active";
5
6 // Called once for every active query, allowing dynamic filtering:
7 onQueryUpdated(observableQuery) {
8 return !shouldIgnoreQuery(observableQuery);
9 },
10});
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) await
ing the Promise
:
1const { queries, results } = client.refetchQueries({
2 // Specific client.refetchQueries options are not relevant to this example.
3});
4
5const finalResults = await Promise.all(
6 results.map((result, i) => {
7 return Promise.resolve(result).catch(error => {
8 console.error(`Error refetching query ${queries[i].queryName}: ${error}`);
9 return null; // Silence this Promise rejection.
10 });
11 })
12});
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 explaining your use case(s).
Corresponding client.mutate
options
For refetching after a mutation, 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 mutation 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.refetchQueries | ⇔ | options.include |
options.update | ⇔ | options.updateCache |
options.onQueryUpdated | ⇔ | options.onQueryUpdated |
options.awaitRefetchQueries | ⇔ | Return a Promise from onQueryUpdated |