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;
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]})
})
)
}