Skip to content

Commit 0523a12

Browse files
fix useReadContract type hinting
1 parent a0835f7 commit 0523a12

File tree

4 files changed

+103
-74
lines changed

4 files changed

+103
-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: 76 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,13 @@ 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 { WithPickedOnceQueryOptions } from "../types.js";
2625

2726
/**
2827
* A hook to read state from a contract that automatically updates when the contract changes.
2928
*
30-
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a contract.
29+
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
30+
* contract.
3131
*
3232
* @param options - The options for reading from a contract
3333
* @returns a UseQueryResult object.
@@ -52,20 +52,19 @@ type PickedQueryOptions = {
5252
* @contract
5353
*/
5454
export function useReadContract<
55-
const abi extends Abi,
56-
const method extends abi extends { length: 0 }
55+
const TAbi extends Abi,
56+
const TMethod extends TAbi extends AbiOfLength<0>
5757
? AbiFunction | string
58-
: ExtractAbiFunctionNames<abi>,
58+
: ExtractAbiFunctionNames<TAbi>,
5959
>(
60-
options: ReadContractOptions<abi, method> & {
61-
queryOptions?: PickedQueryOptions;
62-
},
60+
options: WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
6361
): UseQueryResult<
64-
ReadContractResult<PreparedMethod<ParseMethod<abi, method>>[2]>
62+
ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
6563
>;
6664
/**
6765
* 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.
66+
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
67+
* contract.
6968
*
7069
* @param extension - An extension to call.
7170
* @param options - The read extension params.
@@ -82,38 +81,41 @@ export function useReadContract<
8281
* ```
8382
*/
8483
export function useReadContract<
85-
const abi extends Abi,
86-
const params extends object,
87-
result,
84+
const TAbi extends Abi,
85+
const TParams extends object,
86+
TResult,
8887
>(
89-
extension: (options: BaseTransactionOptions<params, abi>) => Promise<result>,
90-
options: BaseTransactionOptions<params, abi> & {
91-
queryOptions?: PickedQueryOptions;
92-
},
93-
): UseQueryResult<result>;
88+
extension: Extension<TAbi, TParams, TResult>,
89+
options: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
90+
): UseQueryResult<TResult>;
9491

9592
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,
93+
const TAbi extends Abi,
94+
const TMethod extends TAbi extends AbiOfLength<0>
95+
? AbiFunction | `function ${string}` | AsyncGetAbiFunctionFromContract<TAbi>
96+
: ExtractAbiFunctionNames<TAbi>,
97+
const TParams extends object,
98+
TResult,
10799
>(
108100
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-
},
101+
| Extension<TAbi, TParams, TResult>
102+
| WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
103+
options?: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
116104
) {
105+
type QueryKey = readonly [
106+
"readContract",
107+
number | string,
108+
string,
109+
string | PreparedMethod<ParseMethod<TAbi, TMethod>>,
110+
string,
111+
];
112+
type QueryFn = () => Promise<
113+
TResult | ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
114+
>;
115+
116+
let queryKey: QueryKey | undefined;
117+
let queryFn: QueryFn | undefined;
118+
117119
// extension case
118120
if (typeof extensionOrOptions === "function") {
119121
if (!options) {
@@ -123,45 +125,45 @@ export function useReadContract<
123125
}
124126
const { queryOptions, contract, ...params } = options;
125127

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-
});
128+
queryKey = [
129+
"readContract",
130+
contract.chain.id,
131+
contract.address,
132+
getFunctionId(extensionOrOptions),
133+
stringify(params),
134+
] as const;
138135

139-
// TODO - FIX LATER
140-
// biome-ignore lint/correctness/useHookAtTopLevel: <explanation>
141-
return useQuery(query);
136+
queryFn = () =>
137+
extensionOrOptions({
138+
...(params as TParams),
139+
contract,
140+
});
142141
}
143142
// raw tx case
144143
if ("method" in extensionOrOptions) {
145144
const { queryOptions, ...tx } = extensionOrOptions;
146145

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-
});
146+
queryKey = [
147+
"readContract",
148+
tx.contract.chain.id,
149+
tx.contract.address,
150+
tx.method,
151+
stringify(tx.params),
152+
] as const;
153+
154+
queryFn = () => readContract(extensionOrOptions);
155+
}
158156

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

164-
throw new Error(
165-
`Invalid "useReadContract" options. Expected either a read extension or a transaction object.`,
166-
) as never;
163+
return useQuery(
164+
defineQuery({
165+
queryKey: queryKey as QueryKey,
166+
queryFn: queryFn as QueryFn,
167+
}),
168+
);
167169
}
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)