Skip to content

Commit 887af9b

Browse files
theamodhshettytmm
andauthored
Fix generated read hook return type narrowing (#5020)
* fix(react): narrow generated read hook return types * chore: tweaks * chore: tweaks * refactor: use ReadContractCallOptions to avoid duplicate properties in autocomplete --------- Co-authored-by: tmm <tmm@tmm.dev>
1 parent 2a4660e commit 887af9b

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

.changeset/young-foxes-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wagmi": patch
3+
---
4+
5+
Fixed `createUseReadContract` return type inference when multiple view functions share the same argument shape.

packages/react/src/hooks/codegen/createUseReadContract.test-d.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ test('overloads', () => {
100100
}
101101
| undefined
102102
>(result4.data)
103+
104+
useReadViewOverloads({
105+
// @ts-expect-error invalid functionName
106+
functionName: 'invalid',
107+
})
108+
109+
useReadViewOverloads({
110+
functionName: 'foo',
111+
// @ts-expect-error too many args
112+
args: ['0x', '0x', '0x'],
113+
})
103114
})
104115

105116
test('functionName', () => {
@@ -146,3 +157,64 @@ test('functionName with overloads', () => {
146157
| undefined
147158
>(result4.data)
148159
})
160+
161+
test('narrows data for functions with matching argument shapes', () => {
162+
const abiSameArgsDifferentReturns = [
163+
{
164+
type: 'function',
165+
name: 'foo',
166+
stateMutability: 'view',
167+
inputs: [{ name: 'ilk', type: 'bytes32' }],
168+
outputs: [{ type: 'uint256' }],
169+
},
170+
{
171+
type: 'function',
172+
name: 'bar',
173+
stateMutability: 'view',
174+
inputs: [{ name: 'ilk', type: 'bytes32' }],
175+
outputs: [
176+
{ name: 'art', type: 'uint256' },
177+
{ name: 'rate', type: 'uint256' },
178+
],
179+
},
180+
] as const
181+
const useReadContractSameArgs = createUseReadContract({
182+
abi: abiSameArgsDifferentReturns,
183+
})
184+
185+
{
186+
const result = useReadContractSameArgs({
187+
functionName: 'foo',
188+
args: [
189+
'0x0000000000000000000000000000000000000000000000000000000000000000',
190+
],
191+
})
192+
assertType<bigint | undefined>(result.data)
193+
}
194+
{
195+
const result = useReadContractSameArgs({
196+
functionName: 'bar',
197+
args: [
198+
'0x0000000000000000000000000000000000000000000000000000000000000000',
199+
],
200+
})
201+
assertType<readonly [bigint, bigint] | undefined>(result.data)
202+
}
203+
204+
useReadContractSameArgs({
205+
// @ts-expect-error invalid functionName
206+
functionName: 'baz',
207+
})
208+
209+
useReadContractSameArgs({
210+
functionName: 'foo',
211+
// @ts-expect-error wrong args for foo (expects bytes32)
212+
args: [123n],
213+
})
214+
215+
useReadContractSameArgs({
216+
functionName: 'foo',
217+
// @ts-expect-error abi not allowed on generated hook
218+
abi: abiSameArgsDifferentReturns,
219+
})
220+
})

packages/react/src/hooks/codegen/createUseReadContract.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type {
99
QueryParameter,
1010
ScopeKeyParameter,
1111
UnionCompute,
12-
UnionExactPartial,
1312
} from '@wagmi/core/internal'
1413
import type {
1514
ReadContractData,
@@ -21,7 +20,6 @@ import type {
2120
Address,
2221
ContractFunctionArgs,
2322
ContractFunctionName,
24-
ExactPartial,
2523
} from 'viem'
2624
import { useChainId } from '../useChainId.js'
2725
import { useConfig } from '../useConfig.js'
@@ -47,15 +45,16 @@ export type CreateUseReadContractParameters<
4745
| undefined
4846
}
4947

48+
/** Call-level options from ReadContractParameters (excludes abi, address, functionName, args). */
49+
type ReadContractCallOptions<config extends Config> = Omit<
50+
ReadContractParameters<Abi, string, readonly unknown[], config>,
51+
'abi' | 'address' | 'functionName' | 'args'
52+
>
53+
5054
export type CreateUseReadContractReturnType<
5155
abi extends Abi | readonly unknown[],
5256
address extends Address | Record<number, Address> | undefined,
5357
functionName extends ContractFunctionName<abi, stateMutability> | undefined,
54-
///
55-
omittedProperties extends 'abi' | 'address' | 'functionName' =
56-
| 'abi'
57-
| (address extends undefined ? never : 'address')
58-
| (functionName extends undefined ? never : 'functionName'),
5958
> = <
6059
name extends functionName extends ContractFunctionName<abi, stateMutability>
6160
? functionName
@@ -65,10 +64,12 @@ export type CreateUseReadContractReturnType<
6564
selectData = ReadContractData<abi, name, args>,
6665
>(
6766
parameters?: UnionCompute<
68-
UnionExactPartial<
69-
// TODO: Ideally use UnionStrictOmit with omittedProperties (abi, address, functionName)
70-
ReadContractParameters<abi, name, args, config>
71-
> &
67+
{
68+
abi?: undefined
69+
address?: address extends undefined ? Address : undefined
70+
functionName?: functionName extends undefined ? name : undefined
71+
args?: args | undefined
72+
} & Partial<ReadContractCallOptions<config>> &
7273
ScopeKeyParameter &
7374
ConfigParameter<config> &
7475
QueryParameter<
@@ -80,8 +81,7 @@ export type CreateUseReadContractReturnType<
8081
> &
8182
(address extends Record<number, Address>
8283
? { chainId?: keyof address | undefined }
83-
: unknown) &
84-
ExactPartial<Record<omittedProperties, undefined>>,
84+
: unknown),
8585
) => UseReadContractReturnType<abi, name, args, selectData>
8686

8787
export function createUseReadContract<

0 commit comments

Comments
 (0)