npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-chain-query

v1.0.22

Published

Cached and bundled requests for your dapp

Downloads

30

Readme

useChainQuery

A hook that bundles all requests to the blockchain and efficiently provides cached results. Intentionally left to be very flexible and lenient.

With useChainQuery

  • calls are aggregated for a small duration (1 second by default) and sent in one batched call
  • results are cached and re-rendering components won't re-fire calls
  • any required refresh can be triggered manually
function Component() {
  const [name, updateName] = useChainQuery(erc20Address, 'name')
  const [symbol, updateSymbol] = useChainQuery(erc20Address, 'symbol')

  return <p>name: {name?.toString()}</p>
}

Installation

npm i use-chain-query

Importing

import { ethers } from 'ethers'
import { createChainQuery } from 'use-chain-query'
import { abi, contractAddress } from '.'

const provider = ethers.getDefaultProvider('mainnet')
const contract = new ethers.Contract(contractAddress, abi)

export const useChainQuery = createChainQuery(contract.interface, provider)

Usage with React

While useChainQuery does not require to be used with react, its original intent is to be called as a hook. The function takes in an address to which the call is directed to, a name of the function to be called, and arguments (in an array) to pass along.

function Component() {
  const account = useAccount();
  // @param address string: contract address
  // @param functionName string: name of the function to call
  // @param args any[]: arguments
  // @param argsList any[][] (optional): list of arguments
  // @return [result, update]
  // @return result any: ethers interface decoded result
  // @return update function: function that will trigger a new call
  const [ids = [], updateIds] = useChainQuery(contractAddress, 'getOwnedIds', [account])

  return (
    <>
      <h1>Owned Ids for {`${account}`}:</h1>
      {ids.forEach((id, i) => (
        <p>
          {i}: {id.toString()}
        </p>
      ))}
    </>
  )
}

Using useChainQuery as seen above will immediately update the result with new ids as soon as account changes.

An empty array [] is set as the default return parameter for ids as result will be undefined when it has not been returned yet. updateIds will place the call in the call-queue again and refresh the result.

Flexible Nested Component Calls

useChainQuery doesn't mind if arguments are undefined or change. As soon as all elements of the arguments array are well-defined, a request will be sent and the response will trigger a state-update.

function Component() {
  const ids = usePagination(20)

  const [marketItems = []] = useChainQuery(contractAddress, 'getMarketItem', undefined, ids.map(id => [id]))

  return (
    <>
      <h1>Market items</h1>
      {ids.forEach(id => <MarketItem id={id}>)}
    </>
  )
}

function MarketItem({id}) {
  const account = useAccount();

  const [marketItem] = useChainQuery(contractAddress, 'getMarketItem', [id])
  const [balance = ethers.constants.Zero, updateBalance] = useChainQuery(erc20Address, "balanceOf", [account]);

  return (
      <p>
        id {i}: name {marketItem.name} price {ethers.utils.formatEther(marketItem.price)}
      </p>
  )
}

Internally useChainQuery stores every individual function call result in a cache and only ever re-sends a request when an explicit update is needed. That's why it's possible to pass in a list of arguments (this equates to multiple function calls), as in the above example with marketItems and fetch the results for an individual marketItem in a different component without having to worry about any additional requests being made.

Parameters

createChainQuery(
    interface, // ethersjs-interface
    provider, // ethersjs-provider
    maxCallQueue, // maximum number of calls in call queue
    callDelay, // delay for batching calls
    strict // if set to `true` ethers will throw when receiving incorrect data
);

Concatenating ABIs

Currently, useChainQuery requires one complete interface to be passed for initialization. That's why there is a helper function that will concatenate ABIs allowing for one combined interface to be created when multiple contracts are being used.

import { abiConcat } from "use-chain-query";

const combinedInterface = new ethers.utils.Interface(abiConcat([ERC20ABI, ERC721ABI]));
const useChainQuery = createChainQuery(combinedInterface, provider);

Usage with Web3-React

useChainQuery is intended to be used with a provider with a constant connection, though it can also be used with web3-react, making use of the user's provider. However, the allowed chain ids should be restricted as useChainQuery does not check whether the provider is on the correct chain. Alternatively, window.ethereum could also be used.

import { initializeConnector } from "@web3-react/core";
import { MetaMask } from "@web3-react/metamask";

const allowedChainIds = [1];
const [metamask, { useProvider, useAccount }] = initializeConnector(
  (actions) => new MetaMask(actions, false),
  allowedChainIds
);

function InjectProvider() {
  const provider = useProvider();

  useEffect(() => {
    metamask.activate(1);
  }, []);

  useEffect(() => {
    useChainQuery.useStore.getState().updateProvider(provider);
  }, [provider]);

  return null;
}

Usage without React

useChainQuery is a wrapper around zustand's state-management system. This can be directly accessed and listeners for updates can be placed on certain keys.

import { buildCall } from "use-chain-query";

const calls = buildCall(
  contractAddress,
  'getMarketItem',
  undefined,
  [...Array(20)].map((_, id) => [id])
)

const useChainQueryStore = useChainQuery.useStore

// immediately dispatch queries for first 20 market items
useChainQueryStore.getState().dispatchCalls(calls)

// only want to listen for our "subscribed" keys
const subscribedKeys = Object.keys(calls)

const itemUpdateListener = (item) => {
  console.log('update for item registered', item)
}

// listen to updates in cached results only for keys
useChainQueryStore.subscribe(
  (state) => subscribedKeys.map((key) => state.cachedResults[key]),
  itemUpdateListener,
  {
    equalityFn: shallow
  }
)

Alternatively calls can be queued and aggregated individually.

useChainQueryStore.getState().queueCall(target, functionName, args)

Acknowledgements