Is there an existing issue for this?
Package Version
0.0.17
Current Behavior
I think I may have come across a bug in the useContract
hook that surfaces when any of the hook arguments change throughout the lifetime of the component. Will provide an example below using a changing signer
object.
Using the hook to instantiate a contract w/ a signer:
const contract = useContract({
addressOrName: '0xa50f703771d04d9e76e03b20720539fc014aaa40',
contractInterface: Erc20Abi,
signerOrProvider: signer,
});
This seems to work fine if the signer
object is available on first component render (i.e. the first time the hook runs), but if the signer
is undefined
on first render (which it typically would be since account.connector.getSigner()
is an async call), then the contract
object returned by useContract
will not be updated with the proper signer
object, and it will instead take an additional render to see the contract
object you're expecting.
This can be verified by forcing a re-render of the consumer component after signer
is available, but this shouldn't be necessary and is indicative of a bug in the useContract
hook. The logic in the useEffect
block of the hook says "instantiate a new contract object if the contract address, ABI, or signer/provider has changed", which is correct, but because it's being stored in a ref (and not React state) it doesn't trigger the consumer component to re-render after the ref value is updated, causing the component to have a stale version of the contract
object until a subsequent re-render is triggered.
From React useRef docs:
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current
property doesn’t cause a re-render.
Based on the way the hook is written, this bug will also surface when any of the other hook arguments change.
Expected Behavior
When any of the useContract
hook arguments change throughout the lifetime of the component, a newly instantiated contract
object should be provided to the component by the hook.
Steps To Reproduce
import { useState, useEffect } from 'react';
import { useAccount, useContract } from 'wagmi';
import { Signer } from 'ethers';
export default () => {
const [{ data: account }] = useAccount();
const [signer, setSigner] = useState<Signer>();
// get signer
useEffect(() => {
(async () => {
try {
const res = await account?.connector?.getSigner();
setSigner(res);
} catch (e) {
setSigner(undefined);
}
})();
}, [account?.connector]);
// instantiate contract
const contract = useContract({
addressOrName: '0xa50f703771d04d9e76e03b20720539fc014aaa40',
contractInterface: Erc20Abi,
signerOrProvider: signer,
});
useEffect(() => {
// this is incorrectly `null` even after `signer` is available
console.log(contract.signer);
}, [contract]);
return null;
}
Anything else?
This issue may be related to this discussion: #49