mkmkmkmk
mkmkmkmk

Reputation: 87

How to detect transaction confirmation with web3.js and re-render with React

Context: The app is a simple on-chain todo list that allows you to see a list of todos and create new todos.

Problem: When I create new todos and send it onchain using createTask(), I am struggling to check for transaction confirmation, which then allows me to re-render the todo list to display the new input submitted and confirmed onchain.

Tech stack: Web3.js + React

For your reference: I have been following this tutorial: https://www.dappuniversity.com/articles/ethereum-dapp-react-tutorial.

import './App.css';
import Web3 from 'web3'
import { useEffect, useState } from 'react'

import { TODOLIST_ABI, TODOLIST_ADDRESS } from './config'
import Tasks from './Tasks'
import CreateTasks from './CreateTasks'

const App = () => {
  const [acct, setAcct] = useState('')
  const [contract, setContract] = useState([])
  const [task, setTask] = useState([])
  const [taskCount, setTaskCount] = useState(0)
  const [loading, setLoading] = useState(false)

  const loadBlockchainData = async () => {
    setLoading(true)
    const provider = window.ethereum
    try {
      const web3 = await new Web3(provider)
      const acc = await (await web3.eth.requestAccounts())[0]
      setAcct(acc)

      const todo_list = new web3.eth.Contract(TODOLIST_ABI, TODOLIST_ADDRESS)
      setContract(todo_list)

      const taskCount = await todo_list.methods.taskCount().call()
        
      setTaskCount(taskCount)
      for (var i = 1; i <= taskCount; i++) {
        // methods.mymethod.call - call constant method sithout sending any transaction
        const temp_task = await todo_list.methods.tasks(i).call()
        setTask(t => {return [...t, temp_task]})
      }
      setLoading(false) 

    } catch (error) {
      console.log(`Load Blockchain Data Error: ${error}`)
    } 
  }
  

  const loadTasks = async () => {

    const taskCount = await contract.methods.taskCount().call()
    
    setTaskCount(taskCount)
    setTask(() => [])
    for (var i = 1; i <= taskCount; i++) {
      // methods.mymethod.call - call constant method sithout sending any transaction
      const temp_task = await contract.methods.tasks(i).call()
      setTask(t => {return [...t, temp_task]})
    }
  }

  const createTask = async (text) => {

    setLoading(true)
    console.log(`onsubmit: ${text}`)
    await contract.methods.createTask(text).send({from: acct}).once('sent', r => {
      console.log(`Transaction Hash: ${r['transactionHash']}`)
      loadTasks()
    })
    setLoading(false)
  }

  useEffect(() => {
    loadBlockchainData()
  }, [])

  return (
    <>
      <h1>Hello</h1>
      <p>Your account: { acct }</p>
      {loading? (<p>loading...</p>) : (<Tasks task={ task }/>)}
      <CreateTasks contract={ contract } account={ acct } createTask={ createTask }/>
    </>
  )

}

export default App;

Upvotes: 0

Views: 810

Answers (1)

Yilmaz
Yilmaz

Reputation: 49661

  const taskCount = await todo_list.methods.taskCount().call()

in case taskCount=undefined, it will be good practice to run the for-loop inside if statement

 if(taskCount){//for-loop here}

since you are calling the contract method multiple times in a sequence, you are are getting promises each time and one of those promises might get rejected. Imagine the scenario your taskCount is 10 but when i=5 your promise rejected and you get out of loop and catch block runs. In this case you would have only previous tasks captured. To prevent this, you should implement either all promises resolved or none resolves. (atomic transaction)

In this case you should be using Promise.all

if(taskCount){
    Promise.all(
      // because taskCount is string.convert it to number
      Array(parseInt(taskCount))
        .fill() // if taskCount=2, so far we got [undefined,undefined]
        .map((element,index)=>{
            const temp_task = await todo_list.methods.tasks(index).call()
            setTask(t => {return [...t, temp_task]})
            
    })
)

}

Upvotes: 1

Related Questions