Skip to content

Commit 6ca7886

Browse files
fix useReadContract type hinting
1 parent a0835f7 commit 6ca7886

File tree

4 files changed

+110
-74
lines changed

4 files changed

+110
-74
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Abi, AbiFunction } from "abitype";
2+
import type { ThirdwebContract } from "./contract.js";
3+
4+
export type AbiOfLength<TLength> = { length: TLength };
5+
6+
export type AsyncGetAbiFunctionFromContract<TAbi extends Abi> = (
7+
contract: ThirdwebContract<TAbi>,
8+
) => Promise<AbiFunction>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Abi } from "abitype";
2+
import type { BaseTransactionOptions } from "../transaction/types.js";
3+
4+
export type Extension<TAbi extends Abi, TParams extends object, TResult> = (
5+
options: BaseTransactionOptions<TParams, TAbi>,
6+
) => Promise<TResult>;

packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts

Lines changed: 83 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {
44
useQuery,
55
} from "@tanstack/react-query";
66
import type { Abi, AbiFunction, ExtractAbiFunctionNames } from "abitype";
7-
import type { ThirdwebContract } from "../../../../contract/contract.js";
7+
import type {
8+
AbiOfLength,
9+
AsyncGetAbiFunctionFromContract,
10+
} from "../../../../contract/types.js";
11+
import type { Extension } from "../../../../extensions/types.js";
812
import {
913
type ReadContractOptions,
1014
type ReadContractResult,
@@ -17,17 +21,16 @@ import type {
1721
import type { PreparedMethod } from "../../../../utils/abi/prepare-method.js";
1822
import { getFunctionId } from "../../../../utils/function-id.js";
1923
import { stringify } from "../../../../utils/json.js";
20-
21-
type PickedQueryOptions = {
22-
enabled?: boolean;
23-
refetchInterval?: number;
24-
retry?: number;
25-
};
24+
import type {
25+
PickedOnceQueryOptions,
26+
WithPickedOnceQueryOptions,
27+
} from "../types.js";
2628

2729
/**
2830
* A hook to read state from a contract that automatically updates when the contract changes.
2931
*
30-
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a contract.
32+
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
33+
* contract.
3134
*
3235
* @param options - The options for reading from a contract
3336
* @returns a UseQueryResult object.
@@ -52,20 +55,19 @@ type PickedQueryOptions = {
5255
* @contract
5356
*/
5457
export function useReadContract<
55-
const abi extends Abi,
56-
const method extends abi extends { length: 0 }
58+
const TAbi extends Abi,
59+
const TMethod extends TAbi extends AbiOfLength<0>
5760
? AbiFunction | string
58-
: ExtractAbiFunctionNames<abi>,
61+
: ExtractAbiFunctionNames<TAbi>,
5962
>(
60-
options: ReadContractOptions<abi, method> & {
61-
queryOptions?: PickedQueryOptions;
62-
},
63+
options: WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
6364
): UseQueryResult<
64-
ReadContractResult<PreparedMethod<ParseMethod<abi, method>>[2]>
65+
ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
6566
>;
6667
/**
6768
* A hook to read state from a contract that automatically updates when the contract changes.
68-
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a contract.
69+
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
70+
* contract.
6971
*
7072
* @param extension - An extension to call.
7173
* @param options - The read extension params.
@@ -82,38 +84,42 @@ export function useReadContract<
8284
* ```
8385
*/
8486
export function useReadContract<
85-
const abi extends Abi,
86-
const params extends object,
87-
result,
87+
const TAbi extends Abi,
88+
const TParams extends object,
89+
TResult,
8890
>(
89-
extension: (options: BaseTransactionOptions<params, abi>) => Promise<result>,
90-
options: BaseTransactionOptions<params, abi> & {
91-
queryOptions?: PickedQueryOptions;
92-
},
93-
): UseQueryResult<result>;
91+
extension: Extension<TAbi, TParams, TResult>,
92+
options: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
93+
): UseQueryResult<TResult>;
9494

9595
export function useReadContract<
96-
const abi extends Abi,
97-
const method extends abi extends {
98-
length: 0;
99-
}
100-
?
101-
| AbiFunction
102-
| `function ${string}`
103-
| ((contract: ThirdwebContract<abi>) => Promise<AbiFunction>)
104-
: ExtractAbiFunctionNames<abi>,
105-
const params extends object,
106-
result,
96+
const TAbi extends Abi,
97+
const TMethod extends TAbi extends AbiOfLength<0>
98+
? AbiFunction | `function ${string}` | AsyncGetAbiFunctionFromContract<TAbi>
99+
: ExtractAbiFunctionNames<TAbi>,
100+
const TParams extends object,
101+
TResult,
107102
>(
108103
extensionOrOptions:
109-
| ((options: BaseTransactionOptions<params, abi>) => Promise<result>)
110-
| (ReadContractOptions<abi, method> & {
111-
queryOptions?: PickedQueryOptions;
112-
}),
113-
options?: BaseTransactionOptions<params, abi> & {
114-
queryOptions?: PickedQueryOptions;
115-
},
104+
| Extension<TAbi, TParams, TResult>
105+
| WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
106+
options?: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
116107
) {
108+
type QueryKey = readonly [
109+
"readContract",
110+
number | string,
111+
string,
112+
string | PreparedMethod<ParseMethod<TAbi, TMethod>>,
113+
string,
114+
];
115+
type QueryFn = () => Promise<
116+
TResult | ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
117+
>;
118+
119+
let queryKey: QueryKey | undefined;
120+
let queryFn: QueryFn | undefined;
121+
let queryOpts: PickedOnceQueryOptions | undefined;
122+
117123
// extension case
118124
if (typeof extensionOrOptions === "function") {
119125
if (!options) {
@@ -122,46 +128,49 @@ export function useReadContract<
122128
) as never;
123129
}
124130
const { queryOptions, contract, ...params } = options;
131+
queryOpts = queryOptions;
125132

126-
const query = defineQuery({
127-
queryKey: [
128-
"readContract",
129-
contract.chain.id,
130-
contract.address,
131-
getFunctionId(extensionOrOptions),
132-
stringify(params),
133-
] as const,
134-
// @ts-expect-error - TODO: clean up the type issues here
135-
queryFn: () => extensionOrOptions({ ...params, contract }),
136-
...queryOptions,
137-
});
133+
queryKey = [
134+
"readContract",
135+
contract.chain.id,
136+
contract.address,
137+
getFunctionId(extensionOrOptions),
138+
stringify(params),
139+
] as const;
138140

139-
// TODO - FIX LATER
140-
// biome-ignore lint/correctness/useHookAtTopLevel: <explanation>
141-
return useQuery(query);
141+
queryFn = () =>
142+
extensionOrOptions({
143+
...(params as TParams),
144+
contract,
145+
});
142146
}
143147
// raw tx case
144148
if ("method" in extensionOrOptions) {
145149
const { queryOptions, ...tx } = extensionOrOptions;
150+
queryOpts = queryOptions;
146151

147-
const query = defineQuery({
148-
queryKey: [
149-
"readContract",
150-
tx.contract.chain.id,
151-
tx.contract.address,
152-
tx.method,
153-
stringify(tx.params),
154-
] as const,
155-
queryFn: () => readContract(extensionOrOptions),
156-
...queryOptions,
157-
});
152+
queryKey = [
153+
"readContract",
154+
tx.contract.chain.id,
155+
tx.contract.address,
156+
tx.method,
157+
stringify(tx.params),
158+
] as const;
159+
160+
queryFn = () => readContract(extensionOrOptions);
161+
}
158162

159-
// TODO - FIX LATER
160-
// biome-ignore lint/correctness/useHookAtTopLevel: <explanation>
161-
return useQuery(query);
163+
if (!queryKey || !queryFn) {
164+
throw new Error(
165+
`Invalid "useReadContract" options. Expected either a read extension or a transaction object.`,
166+
) as never;
162167
}
163168

164-
throw new Error(
165-
`Invalid "useReadContract" options. Expected either a read extension or a transaction object.`,
166-
) as never;
169+
return useQuery(
170+
defineQuery({
171+
queryKey: queryKey as QueryKey,
172+
queryFn: queryFn as QueryFn,
173+
...(queryOpts ?? {}),
174+
}),
175+
);
167176
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Prettify } from "../../../utils/type-utils.js";
2+
3+
type BasePickedQueryOptions<T = object> = T & {
4+
enabled?: boolean;
5+
};
6+
7+
export type PickedOnceQueryOptions = Prettify<
8+
BasePickedQueryOptions & { refetchInterval?: number; retry?: number }
9+
>;
10+
11+
export type WithPickedOnceQueryOptions<T> = T & {
12+
queryOptions?: PickedOnceQueryOptions;
13+
};

0 commit comments

Comments
 (0)