`useQuery` data invalidated by `useMutation` causing infinite loops (rerender)
See original GitHub issueThe data
reference returned by useQuery
changes when a useMutation
returns the same type, even if it’s the same resulting object.
This behavior causes infinite loops when the mutation call depends on the query data.
Some details:
Fetch policy: cache-first
(default)
The useQuery
doesn’t refetch the data, it just invalidates the reference, making it impossible to correctly track changes through useEffect
dependencies.
From what I understand, it’s replaced by the updated cache
from the mutation
call.
Intended outcome:
- Since this is the exact same data that is returned from the
query.data
and themutation.data.nested.field
the data reference should be kept the same. Now, since it’s complex objects, I understand it may be impossible to compare the results without a custom policy. - A warning helping to understand what’s happening (in development environment) would be great.
- A parameter option for
useQuery
allowing to prevent this behavior. (I tried usinglazyQuery
orrefetch
without success).
Actual outcome:
- data reference changing on every render
- No idea of what’s causing it
- No idea on how to fix it
How to reproduce the issue: given the following GQL schema
type User {
# many complex fields
}
type House {
owner: User
}
query FetchUser($id: ID!) {
fetchUser(id: $id) {
...UserFields
}
}
mutation CreateHouse($ownerId: ID!) {
createHouse(ownerId: $ownerId) {
owner {
...UserFields
}
...OtherFields
}
}
and the following react hook
const useHouseCreator = (userId: string) => {
const {
data: dataFetchUser // changes on every render occuring after the mutation
} = useQuery(FETCH_USER, {
variables: { id: userId },
})
const [
createHouse,
{ data: dataCreateHouse },
] = useMutation(CREATE_HOUSE)
useEffect(() => {
if (dataFetchUser) {
createHouse({ variables: { ownerId: dataFetchUser.fetchUser.id }) // invalidates dataFetchUser
}
},
[dataFetchUser], // dependency is always different
)
return null
}
Additional note:
Removing the User
from the mutation
result…
mutation CreateHouse($ownerId: ID!) {
createHouse(ownerId: $ownerId) {
# owner {
# ...UserFields
# }
...OtherFields
}
}
…prevents the useQuery
data to lose its reference. That’s the reason that makes me think that apollo is replacing dataFetchUser.fetchUser
with dataCreateHouse.createHouse.owner
, thus losing the reference.
Versions
System: OS: Linux 5.15 Ubuntu 20.04.5 LTS (Focal Fossa) Binaries: Node: 16.14.2 - /usr/local/bin/node Yarn: 1.22.18 - /usr/local/bin/yarn Browsers: Chrome: 106.0.5249.91 npmPackages: react: 18.2.0 => 18.2.0 react-dom: 18.2.0 => 18.2.0 typescript: 4.5.5 => 4.5.5 @apollo/client: 3.7.1 => 3.7.1
I don’t think it’s a duplicate of #6760 since they are discussing about the behavior of the fetch policy cache-and-network
.
Also I struggled to find the problem and I first encountered this now closed issue #9204
Issue Analytics
- State:
- Created a year ago
- Comments:8 (4 by maintainers)
Top GitHub Comments
You refetch it as part of your mutation result. You don’t have fetch the whole result of a mutation. You can just leave out parts of the result you’re not interested in.
If you still want to pursue this in Apollo I would suggest you make a new ticket with the narrower revised case, something like: useQuery emits an update after refetch even if data doesn’t change.
You’ve got your user input right there 😄. Trigger the mutation on page load outside of the component.
I understand. That’s a much narrower issue. I think that pleading for your expected behavior to be the default is not likely to be successful. The obvious way to get your behavior is to compare the data before deciding to emit an update. A compare is not cheap, and in most cases the data will have changed, why else did you refetch? I think you can implement a custom caching strategy that does what you want, so the option is available to you; but having this as a default is not likely to be accepted.