wagmi: bug: unnecessary query invalidations in `useWalletClient` & `useConnectorClient`

Describe the bug

vm! šŸ‘‹ We’ve started using wagmi v2 in a bigger project and noticed that some hooks cause too many re-renders, which leads to poor application performance. Both @Yuripetusko and I tried to find the root of the issue earlier today, and turns out the problematic hooks are useWalletClient & possibly useConnectorClient. Both of these contain a useEffect like this:

useEffect(() => {
    // invalidate when address changes
    if (address) queryClient.invalidateQueries({ queryKey })
    else queryClient.removeQueries({ queryKey }) // remove when account is disconnected
}, [address, queryClient])

This looks fine at first glance, but this logic runs every single time when a hook gets called for the first time inside a component, so essentially if we mount a component which contains the hook, it’ll invalidate the query.

Link to Minimal Reproducible Example

https://codesandbox.io/p/sandbox/wagmi-use-wallet-client-2mfmgd

Steps To Reproduce

  1. Open the provided Codesandbox
  2. Connect your wallet
  3. Click on the ā€œRe-render child componentā€ button (one or more times)

You’ll notice that the WalletClient UID changes every time the child component’s key changes.

Wagmi Version

2.5.11

Viem Version

2.8.14

TypeScript Version

5.4.2

Check existing issues

Anything else?

We also tried to find a quick solution for this issue & figured I could post it here, maybe it’ll serve as an inspiration for the final solution. So, I essentially stored the value of the account variable in a ref, and since refs don’t trigger re-renders, I could use it to compare the account with its previous value inside this useEffect:

const prevAddress = useRef<UseAccountReturnType<config>["address"]>(address)

useEffect(() => {
  if (!address) {
    // remove when account is disconnected 
    queryClient.removeQueries({ queryKey })
  } else if (address !== prevAddress.current) {
    // invalidate when address changes
    queryClient.invalidateQueries({ queryKey })
  }

  prevAddress.current = address
}, [address, queryClient])

About this issue

  • Original URL
  • State: closed
  • Created 3 months ago
  • Comments: 15 (15 by maintainers)

Most upvoted comments

Great work all around!

Go for it. I didn’t do much here, was just observing/helping out a bit, but you found a bug and found a fix, I just changed it slightly

ahh right šŸ˜„ sorry. Long day

But if we follow the existing coding convention, then this is better

useEffect(() => {
  if (addressRef.current === null || addressRef.current === address) return
  
  if (address) queryClient.invalidateQueries({ queryKey });
  else queryClient.removeQueries({ queryKey });
  
  addressRef.current = address;
}, [address])

Why assign the value if it’s never even used

Good point, my idea was to keep the ref value up to date, no matter what, since in theory updating a ref’s value shouldn’t cause unnecessary re-renders, but I like your solution, it’s probably better to just assign addressRef.current when it actually changes!

Although my last code perhaps has too much repetition. It can probably be shorter. I personally like verbosity in code, so it’s easier to read and understand without spending too much time. Unlike @tmm and rest of wagmi guys where I noticed that it often looks like Coffeescript šŸ˜† i.e. not adding curly braces for conditions with 1 line. There’s nothing bad with it, but I personally never allow our devs to do this lol.