Skip to content

useReadContracts should not always rely on current chainId #4797

@danielsimao

Description

@danielsimao

Check existing issues

Describe the bug

The current implementation of useReadContracts causes unnecessary query resets when switching chains.

Current Code

export function useReadContracts<
  const contracts extends readonly unknown[],
  allowFailure extends boolean = true,
  config extends Config = ResolvedRegister['config'],
  selectData = ReadContractsData<contracts, allowFailure>,
>(
  parameters: UseReadContractsParameters<
    contracts,
    allowFailure,
    config,
    selectData
  > = {},
): UseReadContractsReturnType<contracts, allowFailure, selectData> {
  const { contracts = [], query = {} } = parameters

  const config = useConfig(parameters)
  const chainId = useChainId({ config })

  const options = readContractsQueryOptions<config, contracts, allowFailure>(
    config,
    { ...parameters, chainId },
  )

  const enabled = useMemo(() => {
    let isContractsValid = false
    for (const contract of contracts) {
      const { abi, address, functionName } =
        contract as ContractFunctionParameters
      if (!abi || !address || !functionName) {
        isContractsValid = false
        break
      }
      isContractsValid = true
    }
    return Boolean(isContractsValid && (query.enabled ?? true))
  }, [contracts, query.enabled])

  return useQuery({
    ...options,
    ...query,
    enabled,
    structuralSharing: query.structuralSharing ?? structuralSharing,
  })
}

Note in particular:

const options = readContractsQueryOptions<config, contracts, allowFailure>(
  config,
  { ...parameters, chainId },
)

Problem

Including chainId in the query key means that switching chains always resets the query, even when the contracts themselves specify a chainId.

This is problematic in cases like the following:

const query = useReadContracts({
    contracts: [
      {
        address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
        chainId: base.id,
        args: [account.address!],
        abi: erc20Abi,
        functionName: 'balanceOf',
      },
      {
        address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', // USDC
        chainId: avalanche.id,
        args: [account.address!],
        abi: erc20Abi,
        functionName: 'balanceOf',
      },
    ],
  });

Here, the contracts themselves define which chainId to use, so relying on the "current chain" is unnecessary and causes extra invalidations.

Suggested Solution

Before falling back to the current chainId, check whether the contracts already define their own chainId.

This would prevent queries from being reset incorrectly when the user switches networks, while still preserving the existing behavior for cases where chainId is not explicitly provided in the contracts.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/new-wagmi-utpexcpt?file=src%2FApp.tsx

Steps To Reproduce

Problem only happens on the first load of each query (before it gets cached by react-query):

  1. load page and wait on contract read fetch to complete
  2. switch to base/avalanche
  3. see that data is refetched

This is unwanted, because those contracts reads are unrelated to the current chain id.

What Wagmi package(s) are you using?

wagmi

Wagmi Package(s) Version(s)

latest

Viem Version

latest

TypeScript Version

latest

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions