Skip to content

Commit

Permalink
[TOOL-2917]: fix useReadContract type hinting (#5888)
Browse files Browse the repository at this point in the history
  • Loading branch information
dirtycajunrice authored Jan 7, 2025
1 parent b058f68 commit c06ecda
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 74 deletions.
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 @@ import {
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 {
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 @@ type PickedQueryOptions = {
* @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<
* ```
*/
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 @@ export function useReadContract<
) 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;

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;

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

// 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;
}

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;
};

0 comments on commit c06ecda

Please sign in to comment.