apollo-client
apollo-client copied to clipboard
useMutation updates cache but does not re-render components querying same object
Intended outcome:
- I am querying for one object (Object A) and checking for the existence of one of its fields (Object B) in a component using
useQuery
(Component A). - In a separate component (Component B), I am using
useMutation
to create Object B. In the payload, I am returning Object A, this time with a non-null Object B. - Component A should re-render since Object A updated in the cache and its Object B field is now not null
- However the original component using useQuery did not re-render, period.
Actual outcome:
- Component A does not re-render.
- Strangely, with the apollo chrome plugin I can confirm that the cache did update
Some screenshots w/ info redacted. Object A is listing
and Object B (the object being created) is latestValidPropertyInspection
.
The initial query:
The mutation running and returning the new objects w/ non-null
latestValidPropertyInspection
:
apollo chrome extension showing the cache updated after the mutation ran:
query:
const result = useQuery({
query: listingById,
options: { variables: { id: currentListing.id } },
});
mutation:
const [requestInspection, result] = useMutation(mutation: requestInspectionMutation);
How to reproduce the issue: Query for a single object in one component, with a field being another object but that is returning null. In another component, mutate to create said object, but return the parent (the original single object). You should find that the component did not render.
Versions
System:
OS: Linux 4.19 Debian GNU/Linux 8 (jessie) 8 (jessie)
Binaries:
Node: 10.11.0 - /usr/local/bin/node
Yarn: 1.9.4 - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/bin/npm
npmPackages:
@apollo/react-hooks: ^3.1.3 => 3.1.3
apollo-cache-inmemory: ^1.6.3 => 1.6.3
apollo-client: ^2.6.4 => 2.6.4
apollo-link: ^1.2.13 => 1.2.13
apollo-link-error: ^1.1.12 => 1.1.12
apollo-link-retry: ^2.2.15 => 2.2.15
apollo-link-state: ^0.4.2 => 0.4.2
apollo-upload-client: ^11.0.0 => 11.0.0
react-apollo: ^3.1.3 => 3.1.3
I am running into a curiously similar caching issue. I have an object with a reference that is initially null
, but that later gets set to a reference to another object that is already in the cache. When this value changes from null
to a reference, the cache indeed updates, but the view is not re-rendered. I'm trying to set up a codesandbox, I'll report back if I get anywhere with it.
edit: @kkitay I attempted to reproduce the error, but so far unfortunately everything works. https://github.com/bstro/react-apollo-error-template I strongly suspect that I have not yet recreated the conditions necessary to produce the error. I'm going to keep working on it tomorrow. If you have any ideas, please let me know, or feel free to create a fork off my fork. 🍻
I found the issue, at least on my end: I was changing the value of the field from null
to a reference to an object that didn't exist in the cache yet.
In the development experience, I would kinda expect Apollo to throw an error (or at least a warning) when an entity in the cache has a field that was set to a reference to an object that doesn't exist in the cache.
Using 3.0.0-beta.38 has the same problem, has someone solved it
I'm encountering a similar issue when I try to mutate edges of an object. For example, let's say I have this in my cache:
Item:abc -> { title: 'parent item', children: [{ __ref: 'Item:def' }] }
Item:def -> { title: 'first child' }
If I mutate the parent (to add another child), my cache updates properly:
Item:abc -> { title: 'parent item', children: [{ __ref: 'Item:def' }, { __ref: 'Item:ghi' }] }
Item:def -> { title: 'first child' }
Item:ghi -> { title: 'second child' }
But no react components are re-rendering, even though the ROOT_QUERY / ROOT_MUTATION are both referencing that parent object:
ROOT_QUERY -> { item(id: 'abc'): { __ref: 'Item:abc' } }
ROOT_MUTATION -> { updateItem({ input: { id: 'abc', child: { id: 'ghi' } } }): { __ref: 'Item:abc' } }
I've reproduced this bug.
My context: Using "@apollo/react-hooks"
So in my case, I'm using useMutation()
to add a new object, even after I add this new object into the underlying cache using the update()
callback, a list component doesn't re-render showing the new object in the list, which uses useQuery()
pulling from the same list in cache.
My only workaround right now is setting the fetchPolicy
to no-cache
on the list view, since the cache being mutated (added to) doesn't force the component to re-render...this is my only option?
Any thoughts on other workarounds?
Similar problem as @ahouchens . Set my query options { fetchPolicy: 'no-cache' } like mentioned and it seems to work. Currently using '@apollo/react-hooks' & '[email protected]'
In my case I ended up pursuing the workaround of declaring refetchQueries
and listing out the same query my list view needed after the mutation in the useMutation
hook.
https://www.apollographql.com/docs/react/data/mutations/#usemutation-api
any update on this? I'm having the same problem, I have a parent component that fetches an array of orders. An order has the following schema.
type Order {
id
status
#...
}
Then, in a child component, I'm updating the status, when the mutation is done, the cache is updated but it doesn't re-render the parent. This is my mutation:
const ACCEPT_ORDER = gql`
mutation acceptOrder($orderId: Int!) {
acceptOrder(order: { id: $orderId }) {
id
pharmacyOrderId
acceptedAt
status
}
}
`;
I had this issues before but now with Apollo Client (v3) it's working
I had this same issue and I resolved it by using _.cloneDeep()
before using writeQuery()
to send the data back to the cache
const nodes = _.cloneDeep(cacheResults.listPosts.nodes);
nodes.unshift(result.createPost);
cache.writeQuery<ListPostsQueryResult, ListPostsQueryArgs>({
query: LIST_POSTS,
variables: {
clientId,
followableId,
sortOrder: SortOrder.DESCENDING,
cursor: cursor,
forward: true
},
data: { listPosts: { ...cacheResults.listPosts, nodes } }
});
I believe the reason is because React was being "smart" and not recognizing I was sending a new object back so it never rerendered the useQuery()
hook I was using. After this is worked fine
The same thing happen with useQuery. I have the same useQuery in two components. When the cache gets updated the re-render of the second component doesn't happen. This is a primary feature. Am I doing something wrong ?
In my case I had simply had to do a hard refresh of my browser while I was in development mode. Pretty simple but just mentioning for other people who end up here after some Googling.
I did 2 things and now it's re-rendering
- use the client singleton exported from the module instead of cache(proxy) from update function.
- add writeQuery in
setTimeout(()=> //query here ,0);
Updates are working if I don't use optimisticResponse otherwise as well. For running optimistic updates I'd to add setTimeout and use the client singleton object.
Same here. After I update a component using a modal, the cache is updated, but, does not trigger a re-render in the main ui. Using client 3.3.6. Any ideas?
@samuelseaton makes sense! Works after I made sure I was properly cloning the object, and not just mutating it.
Same here. After I update a component using a modal, the cache is updated, but, does not trigger a re-render in the main ui. Using client 3.3.6. Any ideas?
Try to use @apollo/client 3.3.5 version. Helped to me
Same problem, using update
function with readQuery
and writeQuery
, cache get updated but React component doesn't.
Tried cache.writeQuery
and client.writeQuery
, same outcome.
Also using cloneDeep
, so it's new object for sure.
I have a similar problem too. When the cache is modified, useQuery hook causes a rerender UNLESS the modified object in the cache is something that was not referenced the first time useQuery rendered. Here is my concrete example:
type Me {
matchmakings: [Matchmaking!]!
...
}
type Matchmaking {
joined: DateTime
...
}
Now I have a component that do useQuery
to fetch "me", and the matchmakings, in a single query.
-
Case number 1: the component is rendered only after "me" has already been queried and is in the cache:
{me && <MyComponent/>}
=> In that case, when I docache.modify
to change one of the matchmaking.joined date, the component rerenders (no bug). -
Case number 2: the component is rendered before "me" is in the cache. => first time the component renders, "me" is null (loading from backend) => later on, when I do
cache.modify
to change one of the matchmaking.joined date referred to by the "Me" object that was fetched meanwhile, the component is not rerendered
It is obviously a bug: the only difference between producing it or not is making sure useQuery
hook will load from the cache from the first time. Otherwise, there is no update when I change an object in the cache that was not referenced from the start.
To fix the bug, "useQuery" should update properly all the cache references which are listened too to trigger a refresh.
In my case I had simply had to do a hard refresh of my browser while I was in development mode. Pretty simple but just mentioning for other people who end up here after some Googling.
This worked for me as well (using the update function)
In my case I had simply had to do a hard refresh of my browser while I was in development mode. Pretty simple but just mentioning for other people who end up here after some Googling.
This worked for me as well (using the update function)
hard refresh not work for me. what do you mean you was in development mode?
In my case I had simply had to do a hard refresh of my browser while I was in development mode. Pretty simple but just mentioning for other people who end up here after some Googling.
This worked for me as well (using the update function)
hard refresh not work for me.
what do you mean you was in development mode?
Running the app locally (localhost). Try killing the server and restarting it, then try using another browser
In my case I had simply had to do a hard refresh of my browser while I was in development mode. Pretty simple but just mentioning for other people who end up here after some Googling.
This worked for me as well (using the update function)
hard refresh not work for me. what do you mean you was in development mode?
Running the app locally (localhost). Try killing the server and restarting it, then try using another browser
oh that doesn't work for me. The initial value in the query is null and after the mutation and the update function. the app doesn't re-render
I have faced exact same issue. I have two components 'A' and 'B', both using the same useMeQuery to fetch user data, but 'B' is rendered only when the user logged in. So my flow looks like this:
- Page gets loaded and component 'A' trigger useMeQuery since no user is logged in response is undefined
- User logs in, useLoginMutation is fired, and then manually useMeQuery
- useMeQuery fetches user data
- Component 'A' renders 'B'
The issue is when the user tries to log out. My logout use useMutation with modify method deleting user data from the cache. The thing is useMeQuery triggers component 'B' with updated cache, but 'A' does not.
My solution is to set in component 'A' useMeQuery:
nextFetchPolicy: 'cache-only'
Same problem, using
update
function withreadQuery
andwriteQuery
, cache get updated but React component doesn't. Triedcache.writeQuery
andclient.writeQuery
, same outcome.Also using
cloneDeep
, so it's new object for sure.
it not work
It would appear that @fromi 's comment is on the right track. I am seeing similar behaviors when adding an item to a list and then subsequently removing that item. Certain cache udpate operations definitely trigger re-renders, but some do not, and they seem to coincide with the state of the query when it was first loaded.
Is the issue really with re-rendering here?
Maybe i'm experiencing a different issue, but my components will re-render okay, they are just doing so with stale data.
Example:
I make the following query
query getPortfolioById ($id: MongoID!){
getPortfolioById(_id: $id) {
_id
...PortfolioSummary
positions {
_id
...FullPosition
symbol {
_id
...SymbolSummary
quote {
_id
latestPrice
changePercent
}
}
}
}
}
Then with my UI, I want to send a mutation to addPosition
As per the Apollo docs on adding an item to a list.
I call cache.modify
on the update
function of the useMutation
like so
cache.modify({
id: cache.identify(portfolio),
optimistic: true,
fields: {
positions(existingPositions, { toReference }) {
return [...existingPositions, toReference(addedPosition?._id)];
},
},
});
Now if i check my cache with the Apollo dev tools (or window.__APOLLO_CLIENT__.cache
) I can see the following:
The cache.modify
was succesful.
If I place console.log
's in my components that are watching the initial getPortfolioById
query - I can see they are triggered after the mutation is succesful.
But the new position has not been added to the data, the InMemoryCache
is stale.
My workaround
Use fetchPolicy: 'cache-and-network'
- this causes the useQueries to hit the network again after cache.modify
which is kind of pointless. I could just have used refetchQueries
.
Would aboslutely love this to be fixed
Okay so i've spent the better part of a day investigating this issue by digging into the source code.
But in the end it turned out to be user error.
Make sure the value you return from your cache.modify is in the correct format
{ __ref: `${__typename}:${_id}` }
Here was my broken cache.modify
cache.modify({
id: cache.identify(portfolio),
fields: {
positions(existingPositions, { toReference }) {
const ref = toReference(addedPosition?._id);
// Note: the issue here is that ref is in the following structure
// { __ref: '6b612136123' }
// I stupidly thought that the _id would be enough to resolve the proper ref
// But if you console.log the `existingPositions` refs, they look like this
// { __ref: 'Position:6b123213}
// They include the typename
return [...existingPositions, ref];
},
},
});
I logged out inside apollo-client, and this was not causing ObservableQuery to reobserve its results from the cache.
So instead of using the toReference
utility - use cache.identify
on the newly added entity
positions(existingPositions) {
if (addedPosition) {
return [
...existingPositions,
{ __ref: cache.identify(addedPosition) },
];
}
return existingPositions;
If you ask me, the toReference
utility is lacking here - as its pretty muich useless. The Reference
that it returns is incorrect.
I've also noticed what @dan-cooke said. useQuery
hook returns stale data, even though cache is updated correctly. Actually the component calling useQuery
won't even re-render, if the mutation call is not at the same component - that is, child components calling mutations that modify parent component query cache will not trigger re-render of the parent component. Really weird behaviour.
Can't really even think of a workaround without making a network request by forcing a refetch
. Using v3.4.16
Confirm no one works , as below. my case is the same as @simonasdev
const [reset, {loading }] = useMutation(ResetToken, {
fetchPolicy: 'no-cache',
// update(cache, { data: { resetSubscriptionToken } }) {
// options1 : not working
// cache.modify({
// id: cache.identify(customer),
// fields: {
// subscriptionToken() {
// return resetSubscriptionToken;
// },
// },
// });
// options2 : not working
// cache.updateQuery({
// query: GET_CUSTOMER_PROFILE,
// }, ({customer}) => ({
// customer: {
// ...customer,
// subscriptionToken: resetSubscriptionToken
// }
// }));
// },
// options3 : not working
refetchQueries: ["getCustomerProfile"],
// options3 : not working
onQueryUpdated(observableQuery) {
// Define any custom logic for determining whether to refetch
// if (shouldRefetchQuery(observableQuery)) {
return observableQuery.refetch();
// }
},
});
I had something similar happen - I was querying for nested documents, which were paginated. The query returned the new data to client as I saw the incoming data in the network request. I then followed the documentation to implement a merge function to merge the data with the old data - and confirmed that this too happened inside the apollo client devtools.
The mistake I was making was that the return of the "merge" function inside the typePolicies
must be exactly the same as the originating response from the server. Even though the data looked all correct inside the apollo client devtools, the react component was looking for an exact match. It was a good opportunity to write a quick article - in case you want to see a better explanation. Nested Pagination and Apollo Type Policies