Skip to content

useWatchBlocks subscribes / unsubscribes on each new Block with WebSocket #4352

Open
@coderodigital

Description

@coderodigital

Check existing issues

Describe the bug

When using useWatchBlocks with a WebSocket connection, the expected behavior is as follows:

  1. Subscribe to new blocks
  2. Receive (onBlock) callbacks without unsubscribing
  3. Unsubscribe when the component using useWatchBlocks is unmounted / modified

The observed behavior is:

  1. Subscribe to new heads
  2. Receive a block, execute onBlock
  3. Unsubscribe as a dependency seems to change in the implementation of useWatchBlocks, probably caused by useEffect in the useWatchBlocks implementation
  4. Re-Subscribe and repeat

It's important to be aware that subscribing / Unsubscribing can be an expensive call on certain API providers (e.g. 10 Compute Units on Alchemy), causing unnecessary spikes in API Usage.

Alchemy Compute Unit Costs

Link to Minimal Reproducible Example

No response

Steps To Reproduce

  1. Configure Wagmi with a WebSocket RPC endpoint
  2. Use the snippets below, which only slightly modify the useWatchBlocks code to get console log outputs
    You will observe that the subscription is closed after each block, and a new subscription is established.

Code to use the modified hook:

  useWatchBlocks({
    emitOnBegin: true,
    chainId: Constants.CHAIN_ID,
    onBlock: (b) => {
      console.log(b)
    }
  })

The modified hook:

'use client'

import {
type Config,
type ResolvedRegister,
type WatchBlocksParameters,
watchBlocks,
} from '@wagmi/core'
import type { UnionCompute, UnionExactPartial } from '@wagmi/core/internal'
import { useEffect } from 'react'
import type { BlockTag } from 'viem'

// Import wagmi config and constants for test
import { config } from '../AppKit'

export type EnabledParameter = {
enabled?: boolean | undefined
}

export type ConfigParameter<config extends Config = Config> = {
config?: Config | config | undefined
}

export type UseWatchBlocksParameters<
includeTransactions extends boolean = false,
blockTag extends BlockTag = 'latest',
config extends Config = Config,
chainId extends
config['chains'][number]['id'] = config['chains'][number]['id'],
> = UnionCompute<
UnionExactPartial<
  WatchBlocksParameters<includeTransactions, blockTag, config, chainId>
> &
ConfigParameter<config> &
EnabledParameter
>

export type UseWatchBlocksReturnType = void

/** https://wagmi.sh/react/hooks/useWatchBlocks */
export function useWatchBlocks<
config extends Config = ResolvedRegister['config'],
chainId extends
config['chains'][number]['id'] = config['chains'][number]['id'],
includeTransactions extends boolean = false,
blockTag extends BlockTag = 'latest',
>(
parameters: UseWatchBlocksParameters<
  includeTransactions,
  blockTag,
  config,
  chainId
> = {} as any,
): UseWatchBlocksReturnType {
const { enabled = true, onBlock, config: _, ...rest } = parameters
const chainId = parameters.chainId

// TODO(react@19): cleanup
// biome-ignore lint/correctness/useExhaustiveDependencies: `rest` changes every render so only including properties in dependency array
useEffect(() => {
  if (!enabled) return
  if (!onBlock) return

  // Slightly modify for console logs
  console.log('WATCH')
  const unwatch = watchBlocks(config, {
    ...(rest as any),
    chainId,
    onBlock,
  })

  return () => {
    console.log('UNWATCH')
    unwatch()
  }
}, [
  chainId,
  config,
  enabled,
  onBlock,
  ///
  rest.blockTag,
  rest.emitMissed,
  rest.emitOnBegin,
  rest.includeTransactions,
  rest.onError,
  rest.poll,
  rest.pollingInterval,
  rest.syncConnectedChain,
])
}

What Wagmi package(s) are you using?

wagmi

Wagmi Package(s) Version(s)

2.12.17

Viem Version

2.21.21

TypeScript Version

5.6.0

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions