Skip to content

Commit

Permalink
feat: useReadContract hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ByteAtATime committed Oct 28, 2024
1 parent 4fe43d0 commit 5f2b8b7
Show file tree
Hide file tree
Showing 2 changed files with 313 additions and 0 deletions.
204 changes: 204 additions & 0 deletions packages/svelte/src/lib/hooks/useReadContract.svelte.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { abi, address, bytecode, chain, config, wait } from '@wagmi/test'
import { expect, test } from 'vitest'
import { testHook } from './test.svelte.js'
import { useReadContract } from './useReadContract.svelte.js'

test(
'default',
testHook(async () => {
const result = $derived.by(
useReadContract(() => ({
address: address.wagmiMintExample,
abi: abi.wagmiMintExample,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const,
})),
)

await expect.poll(() => result.isSuccess).toBeTruthy()

expect(result).toMatchInlineSnapshot(`
{
"data": 4n,
"dataUpdatedAt": 1675209600000,
"error": null,
"errorUpdateCount": 0,
"errorUpdatedAt": 0,
"failureCount": 0,
"failureReason": null,
"fetchStatus": "idle",
"isError": false,
"isFetched": true,
"isFetchedAfterMount": true,
"isFetching": false,
"isInitialLoading": false,
"isLoading": false,
"isLoadingError": false,
"isPaused": false,
"isPending": false,
"isPlaceholderData": false,
"isRefetchError": false,
"isRefetching": false,
"isStale": true,
"isSuccess": true,
"queryKey": [
"readContract",
{
"address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"args": [
"0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC",
],
"chainId": 1,
"functionName": "balanceOf",
},
],
"refetch": [Function],
"status": "success",
}
`)
}),
)

test(
'parameters: chainId',
testHook(async () => {
const result = $derived.by(
useReadContract(() => ({
address: address.wagmiMintExample,
abi: abi.wagmiMintExample,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const,
chainId: chain.mainnet2.id,
})),
)

await expect.poll(() => result.isSuccess).toBeTruthy()

expect(result).toMatchInlineSnapshot(`
{
"data": 4n,
"dataUpdatedAt": 1675209600000,
"error": null,
"errorUpdateCount": 0,
"errorUpdatedAt": 0,
"failureCount": 0,
"failureReason": null,
"fetchStatus": "idle",
"isError": false,
"isFetched": true,
"isFetchedAfterMount": true,
"isFetching": false,
"isInitialLoading": false,
"isLoading": false,
"isLoadingError": false,
"isPaused": false,
"isPending": false,
"isPlaceholderData": false,
"isRefetchError": false,
"isRefetching": false,
"isStale": true,
"isSuccess": true,
"queryKey": [
"readContract",
{
"address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"args": [
"0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC",
],
"chainId": 456,
"functionName": "balanceOf",
},
],
"refetch": [Function],
"status": "success",
}
`)
}),
)

test(
'parameters: config',
testHook(
async () => {
const result = $derived.by(
useReadContract(() => ({
address: address.wagmiMintExample,
abi: abi.wagmiMintExample,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'] as const,
config,
})),
)

await expect.poll(() => result.isSuccess).toBeTruthy()

expect(result).toMatchInlineSnapshot(`
{
"data": 4n,
"dataUpdatedAt": 1675209600000,
"error": null,
"errorUpdateCount": 0,
"errorUpdatedAt": 0,
"failureCount": 0,
"failureReason": null,
"fetchStatus": "idle",
"isError": false,
"isFetched": true,
"isFetchedAfterMount": true,
"isFetching": false,
"isInitialLoading": false,
"isLoading": false,
"isLoadingError": false,
"isPaused": false,
"isPending": false,
"isPlaceholderData": false,
"isRefetchError": false,
"isRefetching": false,
"isStale": true,
"isSuccess": true,
"queryKey": [
"readContract",
{
"address": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
"args": [
"0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC",
],
"chainId": 1,
"functionName": "balanceOf",
},
],
"refetch": [Function],
"status": "success",
}
`)
},
{ shouldMockConfig: false },
),
)

test(
'parameters: deployless read (bytecode)',
testHook(async () => {
const result = $derived.by(
useReadContract(() => ({
abi: abi.wagmiMintExample,
functionName: 'name',
code: bytecode.wagmiMintExample,
})),
)

await expect.poll(() => result.isSuccess).toBeTruthy()

expect(result.data).toMatchInlineSnapshot(`"wagmi"`)
}),
)

test(
'behavior: disabled when properties missing',
testHook(async () => {
const result = $derived.by(useReadContract())

await wait(100)
await expect.poll(() => result.isPending).toBeTruthy()
}),
)
109 changes: 109 additions & 0 deletions packages/svelte/src/lib/hooks/useReadContract.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use client'

import { type CreateQueryReturnType, createQuery } from '$lib/query.svelte.js'
import type {
ConfigParameter,
QueryParameter,
RuneParameters,
RuneReturnType,
} from '$lib/types.js'
import type {
Config,
ReadContractErrorType,
ResolvedRegister,
} from '@wagmi/core'
import type { UnionCompute } from '@wagmi/core/internal'
import {
type ReadContractData,
type ReadContractOptions,
type ReadContractQueryFnData,
type ReadContractQueryKey,
readContractQueryOptions,
structuralSharing,
} from '@wagmi/core/query'
import type { Abi, ContractFunctionArgs, ContractFunctionName, Hex } from 'viem'
import { useChainId } from './useChainId.svelte.js'
import { useConfig } from './useConfig.svelte.js'

export type UseReadContractParameters<
abi extends Abi | readonly unknown[] = Abi,
functionName extends ContractFunctionName<
abi,
'pure' | 'view'
> = ContractFunctionName<abi, 'pure' | 'view'>,
args extends ContractFunctionArgs<
abi,
'pure' | 'view',
functionName
> = ContractFunctionArgs<abi, 'pure' | 'view', functionName>,
config extends Config = Config,
selectData = ReadContractData<abi, functionName, args>,
> = RuneParameters<
UnionCompute<
ReadContractOptions<abi, functionName, args, config> &
ConfigParameter<config> &
QueryParameter<
ReadContractQueryFnData<abi, functionName, args>,
ReadContractErrorType,
selectData,
ReadContractQueryKey<abi, functionName, args, config>
>
>
>

export type UseReadContractReturnType<
abi extends Abi | readonly unknown[] = Abi,
functionName extends ContractFunctionName<
abi,
'pure' | 'view'
> = ContractFunctionName<abi, 'pure' | 'view'>,
args extends ContractFunctionArgs<
abi,
'pure' | 'view',
functionName
> = ContractFunctionArgs<abi, 'pure' | 'view', functionName>,
selectData = ReadContractData<abi, functionName, args>,
> = RuneReturnType<CreateQueryReturnType<selectData, ReadContractErrorType>>

/** https://wagmi.sh/react/api/hooks/useReadContract */
export function useReadContract<
const abi extends Abi | readonly unknown[],
functionName extends ContractFunctionName<abi, 'pure' | 'view'>,
args extends ContractFunctionArgs<abi, 'pure' | 'view', functionName>,
config extends Config = ResolvedRegister['config'],
selectData = ReadContractData<abi, functionName, args>,
>(
parameters: UseReadContractParameters<
abi,
functionName,
args,
config,
selectData
> = () => ({}) as any,
): UseReadContractReturnType<abi, functionName, args, selectData> {
const { abi, address, functionName, query = {} } = $derived.by(parameters)
// @ts-ignore
const code = $derived(parameters().code as Hex | undefined)

const config = $derived.by(useConfig(parameters))
const chainId = $derived.by(useChainId(() => ({ config })))

const options = $derived(
readContractQueryOptions<config, abi, functionName, args>(config, {
...(parameters() as any),
chainId: parameters().chainId ?? chainId,
}),
)
const enabled = $derived(
Boolean(
(address || code) && abi && functionName && (query.enabled ?? true),
),
)

return createQuery(() => ({
...query,
...options,
enabled,
structuralSharing: query.structuralSharing ?? structuralSharing,
}))
}

0 comments on commit 5f2b8b7

Please sign in to comment.