Skip to content

[TOOL-2917]: fix useReadContract type hinting #5888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/thirdweb/src/contract/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Abi, AbiFunction } from "abitype";
import type { ThirdwebContract } from "./contract.js";

export type AbiOfLength<TLength> = { length: TLength };

export type AsyncGetAbiFunctionFromContract<TAbi extends Abi> = (
contract: ThirdwebContract<TAbi>,
) => Promise<AbiFunction>;
6 changes: 6 additions & 0 deletions packages/thirdweb/src/extensions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Abi } from "abitype";
import type { BaseTransactionOptions } from "../transaction/types.js";

export type Extension<TAbi extends Abi, TParams extends object, TResult> = (
options: BaseTransactionOptions<TParams, TAbi>,
) => Promise<TResult>;
157 changes: 83 additions & 74 deletions packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
useQuery,
} from "@tanstack/react-query";
import type { Abi, AbiFunction, ExtractAbiFunctionNames } from "abitype";
import type { ThirdwebContract } from "../../../../contract/contract.js";
import type {
AbiOfLength,
AsyncGetAbiFunctionFromContract,
} from "../../../../contract/types.js";
import type { Extension } from "../../../../extensions/types.js";
import {
type ReadContractOptions,
type ReadContractResult,
Expand All @@ -17,17 +21,16 @@
import type { PreparedMethod } from "../../../../utils/abi/prepare-method.js";
import { getFunctionId } from "../../../../utils/function-id.js";
import { stringify } from "../../../../utils/json.js";

type PickedQueryOptions = {
enabled?: boolean;
refetchInterval?: number;
retry?: number;
};
import type {
PickedOnceQueryOptions,
WithPickedOnceQueryOptions,
} from "../types.js";

/**
* A hook to read state from a contract that automatically updates when the contract changes.
*
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a contract.
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
* contract.
*
* @param options - The options for reading from a contract
* @returns a UseQueryResult object.
Expand All @@ -52,20 +55,19 @@
* @contract
*/
export function useReadContract<
const abi extends Abi,
const method extends abi extends { length: 0 }
const TAbi extends Abi,
const TMethod extends TAbi extends AbiOfLength<0>
? AbiFunction | string
: ExtractAbiFunctionNames<abi>,
: ExtractAbiFunctionNames<TAbi>,
>(
options: ReadContractOptions<abi, method> & {
queryOptions?: PickedQueryOptions;
},
options: WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
): UseQueryResult<
ReadContractResult<PreparedMethod<ParseMethod<abi, method>>[2]>
ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
>;
/**
* A hook to read state from a contract that automatically updates when the contract changes.
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a contract.
* You can use raw read calls or read [extensions](https://portal.thirdweb.com/react/v5/extensions) to read from a
* contract.
*
* @param extension - An extension to call.
* @param options - The read extension params.
Expand All @@ -82,38 +84,42 @@
* ```
*/
export function useReadContract<
const abi extends Abi,
const params extends object,
result,
const TAbi extends Abi,
const TParams extends object,
TResult,
>(
extension: (options: BaseTransactionOptions<params, abi>) => Promise<result>,
options: BaseTransactionOptions<params, abi> & {
queryOptions?: PickedQueryOptions;
},
): UseQueryResult<result>;
extension: Extension<TAbi, TParams, TResult>,
options: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
): UseQueryResult<TResult>;

export function useReadContract<
const abi extends Abi,
const method extends abi extends {
length: 0;
}
?
| AbiFunction
| `function ${string}`
| ((contract: ThirdwebContract<abi>) => Promise<AbiFunction>)
: ExtractAbiFunctionNames<abi>,
const params extends object,
result,
const TAbi extends Abi,
const TMethod extends TAbi extends AbiOfLength<0>
? AbiFunction | `function ${string}` | AsyncGetAbiFunctionFromContract<TAbi>
: ExtractAbiFunctionNames<TAbi>,
const TParams extends object,
TResult,
>(
extensionOrOptions:
| ((options: BaseTransactionOptions<params, abi>) => Promise<result>)
| (ReadContractOptions<abi, method> & {
queryOptions?: PickedQueryOptions;
}),
options?: BaseTransactionOptions<params, abi> & {
queryOptions?: PickedQueryOptions;
},
| Extension<TAbi, TParams, TResult>
| WithPickedOnceQueryOptions<ReadContractOptions<TAbi, TMethod>>,
options?: WithPickedOnceQueryOptions<BaseTransactionOptions<TParams, TAbi>>,
) {
type QueryKey = readonly [
"readContract",
number | string,
string,
string | PreparedMethod<ParseMethod<TAbi, TMethod>>,
string,
];
type QueryFn = () => Promise<
TResult | ReadContractResult<PreparedMethod<ParseMethod<TAbi, TMethod>>[2]>
>;

let queryKey: QueryKey | undefined;
let queryFn: QueryFn | undefined;
let queryOpts: PickedOnceQueryOptions | undefined;

// extension case
if (typeof extensionOrOptions === "function") {
if (!options) {
Expand All @@ -122,46 +128,49 @@
) as never;
}
const { queryOptions, contract, ...params } = options;
queryOpts = queryOptions;

const query = defineQuery({
queryKey: [
"readContract",
contract.chain.id,
contract.address,
getFunctionId(extensionOrOptions),
stringify(params),
] as const,
// @ts-expect-error - TODO: clean up the type issues here
queryFn: () => extensionOrOptions({ ...params, contract }),
...queryOptions,
});
queryKey = [
"readContract",
contract.chain.id,
contract.address,
getFunctionId(extensionOrOptions),
stringify(params),
] as const;

// TODO - FIX LATER
// biome-ignore lint/correctness/useHookAtTopLevel: <explanation>
return useQuery(query);
queryFn = () =>
extensionOrOptions({
...(params as TParams),
contract,
});
}
// raw tx case
if ("method" in extensionOrOptions) {
const { queryOptions, ...tx } = extensionOrOptions;
queryOpts = queryOptions;

Check warning on line 150 in packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L150 was not covered by tests

const query = defineQuery({
queryKey: [
"readContract",
tx.contract.chain.id,
tx.contract.address,
tx.method,
stringify(tx.params),
] as const,
queryFn: () => readContract(extensionOrOptions),
...queryOptions,
});
queryKey = [
"readContract",
tx.contract.chain.id,
tx.contract.address,
tx.method,
stringify(tx.params),
] as const;

Check warning on line 158 in packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts#L152-L158

Added lines #L152 - L158 were not covered by tests

queryFn = () => readContract(extensionOrOptions);
}

Check warning on line 161 in packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts#L160-L161

Added lines #L160 - L161 were not covered by tests

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

Check warning on line 166 in packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/contract/useReadContract.ts#L164-L166

Added lines #L164 - L166 were not covered by tests
}

throw new Error(
`Invalid "useReadContract" options. Expected either a read extension or a transaction object.`,
) as never;
return useQuery(
defineQuery({
queryKey: queryKey as QueryKey,
queryFn: queryFn as QueryFn,
...(queryOpts ?? {}),
}),
);
}
13 changes: 13 additions & 0 deletions packages/thirdweb/src/react/core/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Prettify } from "../../../utils/type-utils.js";

type BasePickedQueryOptions<T = object> = T & {
enabled?: boolean;
};

export type PickedOnceQueryOptions = Prettify<
BasePickedQueryOptions & { refetchInterval?: number; retry?: number }
>;

export type WithPickedOnceQueryOptions<T> = T & {
queryOptions?: PickedOnceQueryOptions;
};
Loading