DEV Community

Cover image for Introducing Web3-Hooks
Lucas Costa
Lucas Costa

Posted on

Introducing Web3-Hooks

For several years, I have been working on Blockchain projects, and for most of them, my library choice is React, because React is usually the best architectural decision for these projects. The most common (and more repetitive) features are those such as sending transactions, reading smart contract functionalities, and keeping UIs synchronized with on-chain data. It has been great working with available blockchain/web3 libraries — but every new dApp meant re-implementing the same patterns: request/response typing, cache keys, polling vs. subscriptions, chain switching, and error boundaries. I kept wishing there was a lightweight, adapter-agnostic library that gave me the primitives and opinions I needed, without locking me into a single provider stack.

So I decided to build Web3-Hooks.

The goal with Web3-Hooks is to provide a modular React hooks library for Web3 that cleanly separates concerns. You can swap transports and providers (e.g., Viem, Ethers, custom JSON-RPC), keep React-specific concerns in React, and scale your codebase without coupling every component to a specific SDK.

Why another Hooks library?

  • Adapter freedom. Many libraries bundle wallet connectors, transport, and React state in one box. That’s great when you want batteries included, but it can create problems for teams who need to mix & match RPCs (Alchemy/Infura/self-hosted), add custom middleware (retries, tracing), or target non-EVM chains later.
  • Predictable caching. Reads in dApps are perfect for TanStack Query (React Query): cache keys, stale times, SSR awareness, and deduplication eliminate flicker and wasted requests.
  • Typed flows end-to-end. Strong typing across JSON-RPC requests, hook signatures, and responses reduces “unknown hex” and any drift, especially with BigInt and 0x-prefixed data.

Architecture

Web3-Hooks ships as three composable layers:

Web3-Hooks composable layers

1) @web3-hooks/core — the Base Layer (Framework-agnostic)

  • A tiny set of types, interfaces, and utilities that describe how a Web3 client performs requests. No React here.
  • Defines a Web3Client interface (request, subscribe?, chainId?, etc.)
  • Shapes for requests/responses and utilities for creating deterministic query keys
  • Intentionally unopinionated about transport—it can be JSON-RPC over HTTPS, a WebSocket, or something custom

Think of core as the protocol your app speaks to the chain, independent of React.

2) @web3-hooks/adapter-evm-viem — the EVM Adapter (Viem-powered)

A small adapter that implements the core interface using Viem. It:

  • Bridges core to Viem’s publicClient/walletClient
  • Normalizes hex, numbers, and BigInts
  • Provides EVM-specific actions (e.g., eth_chainId, eth_getBalance, log filters)

Swap this adapter out for others (E.g., Ethers, Solana, Starknet) later without touching your React code.

3) @web3-hooks/react — the React Layer (TanStack Query inside)

The React package exposes hooks (e.g., useBlockNumber, useBalance, useLogs) and a Web3Provider that injects your client into context, then leverages TanStack Query to cache/dedupe requests, manage refetching intervals, and provide optimistic UI patterns for writes.

React code depends only on the core contracts and query semantics. The actual RPC “how” is an adapter concern.

Design Principles

  • Small, sharp interfaces. Each layer does one thing well.
  • Predictable cache keys. JSON-stable query keys ensure deduplication across components.
  • Zero UI opinions. Bring your own design system (or none).
  • Escape hatches. Drop to raw RPC when needed. Nothing prevents you from calling custom methods.

Quick Start (Preset EVM)

If you want everything wired for an EVM dApp, use your preset bundle (or wire the three packages manually).

pnpm add @web3-hooks/react @web3-hooks/core @web3-hooks/adapter-evm-viem @tanstack/react-query viem react react-dom
Enter fullscreen mode Exit fullscreen mode
// app root (Next.js or CRA)
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Web3Provider } from '@web3-hooks/react'
import { createClient } from '@web3-hooks/core'
import { viemAdapter } from '@web3-hooks/adapter-evm-viem'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const queryClient = new QueryClient()

const client = createClient(
  viemAdapter({
    publicClient: createPublicClient({
      chain: mainnet,
      transport: http('https://ethereum.publicnode.com')
    })
  })
)

export default function App({ children }) {
  return (
    <QueryClientProvider client={queryClient}>
      <Web3Provider client={client}>
        {children}
      </Web3Provider>
    </QueryClientProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Read the chain data with hooks:

import { useBlockNumber, useBalance } from '@web3-hooks/react'

export function Dashboard({ address }: { address: `0x${string}` }) {
  const { data: blockNumber } = useBlockNumber({ refetchInterval: 4_000 })
  const { data: balance } = useBalance({ address, unit: 'ether' })

  return (
    <div>
      <p>Block: {blockNumber ?? ''}</p>
      <p>Balance: {balance ?? ''} ETH</p>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Under the hood, each hook constructs stable query keys (e.g., ['blockNumber', chainId]) and leverages React Query for caching/deduplication.

How It Works (Deep Dive)

  1. Component calls hook → useBlockNumber({ chainId })
  2. Hook builds a deterministic query key and hands a queryFn to TanStack Query
  3. Query function calls client.request({ method: 'eth_blockNumber', params: [] })
  4. The adapter (e.g., Viem) executes the request (HTTP/WebSocket)
  5. The core layer normalizes/validates the response (hex → BigInt/number/string as configured)
  6. React Query caches the result and dedupes concurrent calls
  7. Refetching is handled by stale times, visibility focus, or explicit refetchInterval

UseSendTransaction workflow

For writes:

  • A hook (e.g., useSendTransaction) calls walletClient.sendTransaction(...)
  • You can optimistically update UI or invalidate queries on receipt Errors bubble through React Query’s error states/error boundaries

SSR & Next.js: Because TanStack Query plays well with SSR, you can prefetch critical reads on the server (where appropriate), and hydrate on the client for instant UI.

Where It Fits Among Other Tools

  • Viem — a modern, typed, EVM toolkit. web3-hooks uses it via the EVM adapter today.
  • Ethers.js — the classic EVM library. An Ethers adapter could be added; the React layer wouldn’t change.
  • wagmi — a feature-rich React EVM library with connectors and actions. If you want more control over transport/adapters (or you’re mixing chains), web3-hooks gives you a lean alternative.
  • The Graph — for indexed data and historical queries. web3-hooks focuses on live RPC reads/writes; combine them when you need richer analytics/history.
  • TypeChain — generate TypeScript typings for contract ABIs. Works great alongside web3-hooks for type-safe contract calls.

Roadmap

  • Additional adapters: Ethers, WS transports, non-EVM chains (Solana/Starknet)
  • More hooks: useFeeHistory, useEnsName, useTokenList, useBundlePrice
  • Event streams via WebSocket subscriptions with backoff/retry
  • DevTools integ ration (React Query + request tracing)

If you’ve use cases (rollups, L2 gas oracles, MEV-resistant patterns), I’d love to hear about them.

Contributing

  • Issues/Ideas: Open a GitHub issue describing the use-case and API shape you expect
  • PRs: Add tests, update docs, and keep adapters small and isolated
  • Discussions: Email/LinkedIn/PR threads

If you maintain infra at Alchemy/Infura or work on chain clients, I’d love input on transport ergonomics and best-practice retries/backoff.

References & Further Reading

Top comments (0)