question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`useQuery` data invalidated by `useMutation` causing infinite loops (rerender)

See original GitHub issue

The 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:

  1. Since this is the exact same data that is returned from the query.data and the mutation.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.
  2. A warning helping to understand what’s happening (in development environment) would be great.
  3. A parameter option for useQuery allowing to prevent this behavior. (I tried using lazyQuery or refetch without success).

Actual outcome:

  1. data reference changing on every render
  2. No idea of what’s causing it
  3. 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:closed
  • Created a year ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
FritsvanCampencommented, Nov 7, 2022

I didn’t refetch. It’s the result of the mutation that is updating the query result reference

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.

1reaction
FritsvanCampencommented, Nov 7, 2022

The mutation is likely triggered by some user input somewhere, hopefully not too far away.

The component is rendered when the user visits/lands on the page. There’s no user input up the stream.

You’ve got your user input right there 😄. Trigger the mutation on page load outside of the component.

my concern is about the fact that I lose the reference on a data that is updated (apparently with the cache) with the exact same object. Thus, a cache object that don’t need an update.

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

javascript - useMutation stuck in inifnite loop
saveVisa runs on every render. Since it's a mutation, it will make the page re-render, trapping you in an infinite loop.
Read more >
Updates from Mutation Responses | TanStack Query Docs
When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response...
Read more >
Handling operation errors - Apollo GraphQL Docs
These are errors encountered while attempting to communicate with your GraphQL server, usually resulting in a 4xx or 5xx response status code (and...
Read more >
Keep getting an infinite loop in my react code? : r/graphql
Invoking the hook like that is changing the state, which causes the component to rerender and repeat.
Read more >
API Slices: React Hooks
useMutation , matching how you defined that endpoint. ... The generated UseMutation hook will cause a component to re-render by default ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found