Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rewrite useScaffoldEventHistory hook #869

Merged
merged 15 commits into from
Jun 25, 2024
Merged

rewrite useScaffoldEventHistory hook #869

merged 15 commits into from
Jun 25, 2024

Conversation

technophile-04
Copy link
Collaborator

@technophile-04 technophile-04 commented Jun 23, 2024

Description:

NOTE: Don't use #633 its bit outdated checkout To Test section for reproducible :

Rewrote useScaffoldEventHistory with useInfiniteQuery from tanstack-query.

Our present logic felt like a bit try-hard solution to get it working also introducing lots of imperative logic and also have a bug example #633.

I have been diving deep in to tanstack-query recently and also had #633 in back of mind for a while, and useInfiniteQuery seems like a perfect solution for this with some additional advantage:

  1. Solves bug: useScaffoldEventHistory multi-chain inconsistent behaviour #633

  2. Cleans up the code nicely

  3. feature like refetch, it will allow people to imperatively fetch events again after a write is performed(without needing for watch) :

    Example :
      const { data: eventHistory, refetch: refetchAllEvents } = useScaffoldEventHistory({
        contractName: "YourContract",
        eventName: "GreetingChange",
        filters: { greetingSetter: connectedAddress },
        fromBlock: 0n,
    });
    
    // ...
    
     <button
          className="btn btn-primary"
          onClick={async () => {
            try {
              await writeYourContractAsync({
                functionName: "setGreeting",
                args: [newGreetings],
              });
             // refetch events again so that UI is reactive and shows the latest event
              await refetchAllEvents();
            } catch (e) {
              console.error("Error setting greeting:", e);
            }
          }}
        >
      Set Greeting
    </button>

Testing steps :

  1. Switch to main branch.
2. Copy this in externalContract.ts :
import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";

const externalContracts = {
  11155111: {
    YourContract: {
      address: "0xE009aea21af005e6B531B5f4a8f909C64A0c596d",
      abi: [
        {
          inputs: [
            {
              internalType: "address",
              name: "_owner",
              type: "address",
            },
          ],
          stateMutability: "nonpayable",
          type: "constructor",
        },
        {
          anonymous: false,
          inputs: [
            {
              indexed: true,
              internalType: "address",
              name: "greetingSetter",
              type: "address",
            },
            {
              indexed: false,
              internalType: "string",
              name: "newGreeting",
              type: "string",
            },
            {
              indexed: false,
              internalType: "bool",
              name: "premium",
              type: "bool",
            },
            {
              indexed: false,
              internalType: "uint256",
              name: "value",
              type: "uint256",
            },
          ],
          name: "GreetingChange",
          type: "event",
        },
        {
          inputs: [],
          name: "greeting",
          outputs: [
            {
              internalType: "string",
              name: "",
              type: "string",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "owner",
          outputs: [
            {
              internalType: "address",
              name: "",
              type: "address",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "premium",
          outputs: [
            {
              internalType: "bool",
              name: "",
              type: "bool",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [
            {
              internalType: "string",
              name: "_newGreeting",
              type: "string",
            },
          ],
          name: "setGreeting",
          outputs: [],
          stateMutability: "payable",
          type: "function",
        },
        {
          inputs: [],
          name: "totalCounter",
          outputs: [
            {
              internalType: "uint256",
              name: "",
              type: "uint256",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [
            {
              internalType: "address",
              name: "",
              type: "address",
            },
          ],
          name: "userGreetingCounter",
          outputs: [
            {
              internalType: "uint256",
              name: "",
              type: "uint256",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "withdraw",
          outputs: [],
          stateMutability: "nonpayable",
          type: "function",
        },
        {
          stateMutability: "payable",
          type: "receive",
        },
      ],
      inheritedFunctions: {},
    },
  },
  84532: {
    YourContract: {
      address: "0x8806fc80A0274Eda6a45E2944f6bB6E6Bb635831",
      abi: [
        {
          inputs: [
            {
              internalType: "address",
              name: "_owner",
              type: "address",
            },
          ],
          stateMutability: "nonpayable",
          type: "constructor",
        },
        {
          anonymous: false,
          inputs: [
            {
              indexed: true,
              internalType: "address",
              name: "greetingSetter",
              type: "address",
            },
            {
              indexed: false,
              internalType: "string",
              name: "newGreeting",
              type: "string",
            },
            {
              indexed: false,
              internalType: "bool",
              name: "premium",
              type: "bool",
            },
            {
              indexed: false,
              internalType: "uint256",
              name: "value",
              type: "uint256",
            },
          ],
          name: "GreetingChange",
          type: "event",
        },
        {
          inputs: [],
          name: "greeting",
          outputs: [
            {
              internalType: "string",
              name: "",
              type: "string",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "owner",
          outputs: [
            {
              internalType: "address",
              name: "",
              type: "address",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "premium",
          outputs: [
            {
              internalType: "bool",
              name: "",
              type: "bool",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [
            {
              internalType: "string",
              name: "_newGreeting",
              type: "string",
            },
          ],
          name: "setGreeting",
          outputs: [],
          stateMutability: "payable",
          type: "function",
        },
        {
          inputs: [],
          name: "totalCounter",
          outputs: [
            {
              internalType: "uint256",
              name: "",
              type: "uint256",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [
            {
              internalType: "address",
              name: "",
              type: "address",
            },
          ],
          name: "userGreetingCounter",
          outputs: [
            {
              internalType: "uint256",
              name: "",
              type: "uint256",
            },
          ],
          stateMutability: "view",
          type: "function",
        },
        {
          inputs: [],
          name: "withdraw",
          outputs: [],
          stateMutability: "nonpayable",
          type: "function",
        },
        {
          stateMutability: "payable",
          type: "receive",
        },
      ],
      inheritedFunctions: {},
    },
  },
} as const;

export default externalContracts satisfies GenericContractsDeclaration;
3. Copy this in `app/page.tsx` :
"use client";

import type { NextPage } from "next";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";

const Home: NextPage = () => {
  const { data: eventHistory } = useScaffoldEventHistory({
    contractName: "YourContract",
    eventName: "GreetingChange",
    fromBlock: 4738147n,
  });

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
          </h1>
          <div className="overflow-x-auto">
            <table className="table">
              {/* head */}
              <thead>
                <tr>
                  <th></th>
                  <th>Gretting Setter</th>
                  <th>greeting</th>
                </tr>
              </thead>
              <tbody>
                {eventHistory?.map((event, index) => (
                  <tr key={index}>
                    <th>{index + 1}</th>
                    <td>
                      <Address address={event.args.greetingSetter} />
                    </td>
                    <td>{event.args.newGreeting}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;
  1. Update scaffold.config.ts#targetNetworks with [chains.sepolia, chains.baseSepolia].

  2. yarn start.

  3. Connect wallet and switch network to baseSepolia you will see that instead of showing just 3 events it appended the events to previously fetched events from sepolia

    Demo: bug video :
    Screen.Recording.2024-06-23.at.5.06.18.PM.mov
  4. Now switch to this branch(no need to stash above changes, you could directly switch to this PR branch) and try test point 6 again it should work as expected and solve bug: useScaffoldEventHistory multi-chain inconsistent behaviour #633.

    Demo: bug is solved :
    Screen.Recording.2024-06-23.at.5.09.09.PM.mov

Testing watch mode:

Just to make sure we don't break anything and watch mode also works perfectly, you revert all the above changes keep everything clean switch to this branch. Run three magical command yarn chain, yarn deploy and yarn start after that copy this in app/page.tsx

Example :
"use client";

import { useState } from "react";
import type { NextPage } from "next";
import { useAccount } from "wagmi";
import { Address, InputBase } from "~~/components/scaffold-eth";
import { useScaffoldEventHistory, useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();

  const [newGreetings, setNewGreetings] = useState<string>("");

  const {
    data: eventHistory,
    status,
    isFetchingNewEvent,
  } = useScaffoldEventHistory({
    contractName: "YourContract",
    eventName: "GreetingChange",
    filters: { greetingSetter: connectedAddress },
    fromBlock: 0n,
    watch: true,
  });

  const { writeContractAsync: writeYourContractAsync } = useScaffoldWriteContract("YourContract");

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
          </h1>
          <div className="overflow-x-auto">
            <InputBase value={newGreetings} onChange={setNewGreetings} />
            <button
              className="btn btn-primary"
              onClick={async () => {
                try {
                  await writeYourContractAsync({
                    functionName: "setGreeting",
                    args: [newGreetings],
                  });
                } catch (e) {
                  console.error("Error setting greeting:", e);
                }
              }}
            >
              Set Greeting
            </button>
            <table className="table">
              {/* head */}
              <thead>
                <tr>
                  <th></th>
                  <th>Gretting Setter</th>
                  <th>greeting</th>
                </tr>
              </thead>
              <tbody>
                {status === "pending" && (
                  <tr className="animate-pulse">
                    <th>
                      <span className="loading loading-dots loading-xs"></span>
                    </th>
                    <td>
                      <span className="loading loading-dots loading-xs"></span>
                    </td>
                    <td>
                      <span className="loading loading-dots loading-xs"></span>
                    </td>
                  </tr>
                )}
                {eventHistory?.map((event, index) => (
                  <tr key={index}>
                    <th className={`${isFetchingNewEvent ? "animate-pulse opacity-80" : ""}`}>{index + 1}</th>
                    <td className={`${isFetchingNewEvent ? "animate-pulse opacity-80" : ""}`}>
                      <Address address={event?.args.greetingSetter} />
                    </td>
                    <td className={`${isFetchingNewEvent ? "animate-pulse opacity-80" : ""}`}>
                      {event?.args.newGreeting}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;

Set the greeting on home page and see it updating


Also types should also work as expected and consuming it still remains same, but we need to document some new return values like refetch, isFetchingNewEvents in SE-2 docs

@rin-st
Copy link
Collaborator

rin-st commented Jun 24, 2024

Great changes! And thank you for testing steps! 🙏

@rin-st
Copy link
Collaborator

rin-st commented Jun 24, 2024

https://github.com/scaffold-eth/scaffold-eth-2/pull/869/files#diff-0efe8962fd8674c8a04310503b57e5e0ff36eb7016ae11764e1cc83472bc7646R63

As I understand there's no polling interval anymore

upd. My bad, it works as expected

Copy link
Collaborator

@rin-st rin-st left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working great, thank you!

@rin-st
Copy link
Collaborator

rin-st commented Jun 24, 2024

This should possibly be added to recipes in docs

 <button
      className="btn btn-primary"
      onClick={async () => {
        try {
          await writeYourContractAsync({
            functionName: "setGreeting",
            args: [newGreetings],
          });
         // refetch events again so that UI is reactive and shows the latest event
          await refetchAllEvents();
        } catch (e) {
          console.error("Error setting greeting:", e);
        }
      }}
    >
  Set Greeting
</button>

@technophile-04
Copy link
Collaborator Author

Thanks Rinat and Damu review, updated it with suggested changes

Copy link
Collaborator

@rin-st rin-st left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm!

@technophile-04 technophile-04 merged commit a78be87 into main Jun 25, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants