Writing directly to cache does not synchronously update `useQuery` return values
See original GitHub issueIntended outcome: When I write directly to my InMemoryCache
, I want the updated data to be used the next time my React component renders.
Actual outcome: When I write directly to my InMemoryCache
, there is a short time period in which my React component uses old data.
This can cause bugs if you have code that assumes that writing to the cache immediately updates the relevant useQuery
instances. Here’s a real world example: imagine the user has just joined an organization in a multi-tenant application. The code might do the following:
- Write to the cache to add a new organization to
currentUser.organizations
. - Use React Router’s
navigate
function to go to the new organization’s URL. - (Now the code explodes because the URL specifies an organization which is not contained in
currentUser.organizations
.)
How to reproduce the issue:
- View my CodeSandbox.
- Open the console in the right pane.
- Click “Add 1 to rates”.
You should get output similar to the following. Notice how “Direct read” and “useQuery” are out of sync in the first rerender.
Direct read: 4.673000143247005
useQuery: 3.6730001432470056
-------------------
Direct read: 4.673000143247005
useQuery: 4.673000143247005
-------------------
Versions
Issue Analytics
- State:
- Created a year ago
- Comments:6 (2 by maintainers)
Top Results From Across the Web
Advanced topics on caching in Apollo Client
By default, the contents of your cache are immediately restored asynchronously, and they're persisted on every write to the cache with a short...
Read more >Fetch, Cache, and Update Data Effortlessly with React Query
React Query caches data based on the query key so choosing a proper key for your query is very important. You need to...
Read more >react-query getting old data - Stack Overflow
My updateItem API function returns the single updated item from the server. I used setQueryData to solve this.
Read more >QueryClient | TanStack Query Docs
setQueryData is a synchronous function that can be used to immediately update a query's cached data. If the query does not exist, it...
Read more >React-Query: You Might Not Need State Management ♂️
Using react-query allows us to fetch, update, caching data async very ... This hook has some very useful return values, some of them...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I can foresee wanting cache updates to be applied synchronously in the longer term. Synchronous updates are useful for situations like the one I described in the original post. There is also just less potential for bugs when things are synchronous. Though, I understand that batching updates can be more efficient.
I think the bigger issue is that a method like
cache.modify
appears to be synchronous since it does not return aPromise
, but its actual behavior is asynchronous.modify
updates the cache immediately, but the method is still asynchronous when viewed in the larger context of a React application.This discrepancy between the method’s expected and actual behavior could be remedied in two ways:
A) Make
cache.modify
update all observers synchronously, OR B) Makecache.modify
return aPromise
that resolves once all observers have been updated.Since Apollo Client is (more or less) a global state manager, it could be useful to look at what other global state management libraries do. For example, I am pretty sure that Redux + react-redux does not do any batching unless you explicitly use the
batch
function that they export.Our real code is using
cache.modify
, and there does not seem to be an equivalent method onclient
. Here’s a workaround:The
as any
is necessary becauseApolloClient.queryManager
is a private property. But it’s only private from TypeScript’s point of view — the property is still accessible at runtime.