1. Handling transaction lifecycle

1.1. Why does it matter?

The weakest point of interacting with smart contracts is the speed of sending transactions and waiting for results to be returned from the node. This is a characteristic that heavily affects the user experience (UX). Transactions can encounter problems such as errors, reverts, lack of gas, etc., so when designing the user interface, we must take care to handle the entire transaction lifecycle so that users can know the current status clearly.

1.2. What does Wagmi do?

React Hooks in Wagmi have many features to help you manage the transaction lifecycle. Eg:

useReadContract

Wagmi's useReadContract hook returns us the data when calling function without changing state. In addition, the hook also returns a status object (e.g. error, isPending) so we can show the user the corresponding transaction status.

// import BaseError and hook useReadContract
import { type BaseError, useReadContract } from 'wagmi'
// import the smart contract's abi file to get the function's interface
import { abi } from './abi'

function ReadContract() {
  const { 
    data: balance, // assign the returned data to the balance variable
    error, // initialize error variable
    isPending // initialize the isPending variable
  } = useReadContract({
    abi, // abi của function
    functionName: 'balanceOf', // function name you want to call
    args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], // Pass variables to the function
  })
  
  // If isPending is true, the text "Loading..." is displayed, otherwise it disappears
  if (isPending) return <div>Loading...</div> 
  
  // If there is an error, display the div with error message
  if (error) 
    return ( 
      <div>
        Error: {(error as BaseError).shortMessage || error.message} 
      </div> 
    )  

  return (
    // Displays the balance (if any) after converting to string format
    <div>Balance: {balance?.toString()}</div>
  )
}

useWriteContract

Wagmi's useWriteContract hook returns us a data object containing the hash of the transaction after calling a function that changes the state of the smart contract.

data
`WriteContractReturnType | undefined`

Defaults to `undefined`
The last successfully resolved data for the mutation.

Additionally, the hook also returns us an isPending object that we can use to display the pending status of the transaction.

In addition, Wagmi also provides useWaitForTransactionReceipt hook for us to wait for transaction results with 2 return variables: isLoading, isSuccess.

Here is an example from the Wagmi v2 docs:

import * as React from 'react' // import react into file
// import BaseError, and 2 hooks useWaitForTransactionReceipt, useWriteContract from wagmi library
import { 
  type BaseError, 
  useWaitForTransactionReceipt, 
  useWriteContract 
} from 'wagmi'
// import the smart contract's abi file to get the function's interface
import { abi } from './abi'
 
export function MintNFT() {
  const { 
    data: hash, // assign the returned data to a variable named hash
    error, // assign error object to error variable
    isPending, // assign the isPending object to the isPending variable
    writeContract // initialize the writeContract function for use
  } = useWriteContract() 

  // function dùng để submit form
  async function submit(e: React.FormEvent<HTMLFormElement>) { 
    e.preventDefault() 
    const formData = new FormData(e.target as HTMLFormElement) 
    const tokenId = formData.get('tokenId') as string 
    writeContract({
      address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', // contract address
      abi, // abi of contract
      functionName: 'mint', // function name you want to call
      args: [BigInt(tokenId)], // pass input to function
    })
  } 

  // Call the useWaitForTransactionReceipt hook to initialize the isConfirming and isConfirmed states
  const { isLoading: isConfirming, isSuccess: isConfirmed } = 
    useWaitForTransactionReceipt({ 
      hash, 
    }) 

  // Return format of React
  return (
    <form onSubmit={submit}>
      <input name="address" placeholder="0xA0Cf…251e" required />
      <input name="value" placeholder="0.05" required />
      // If isPending is true, the button will be disabled
      <button 
        disabled={isPending} 
        type="submit"
      >
        {isPending ? 'Confirming...' : 'Mint'} 
        // If isPending is true, display the word "Confirming...", otherwise display the word "Mint"
      </button>
      // If hash is true, the div containing the transaction hash is displayed, otherwise it disappears
      {hash && <div>Transaction Hash: {hash}</div>}
      // If isConfirming is true then display the div with the text "Waiting for confirmation..."
      {isConfirming && <div>Waiting for confirmation...</div>} 
      // If isConfirmed is true, display the div with the text "Transaction confirmed."
      {isConfirmed && <div>Transaction confirmed.</div>}
      // If there is an error, display the div with error message
      {error && ( 
        <div>Error: {(error as BaseError).shortMessage || error.message}</div> 
      )} 
    </form>
  )
}