Issue
I am trying to create a simple form which accepts two strings, stores them in state hooks, and clears itself onSubmit
, but it seems that updating the state hook via the input's onChange
(i.e. onChange={(t) => setUserSide(t.target.value)}
) has the effect of clearing whatever was just typed, resulting in an unusable field. When this line is removed, the input retains data, but of course, it is no longer stored in the state hook.
My understanding is/was that updating a state hook via its corresponding setter method produces no side effects, although that seems not to be the case here. Things work as expected though, in a simpler test app.
The parts of the code which I think are relevant
Hooks:
const [newWager, setWager] = useState(0);
const [userSide, setUserSide] = useState(0);
Form:
const Home = () => {
return(
<div className="main">
<div className="upper">
1: {retrievedSide1} : {potFor} WC<br/> 2: {retrievedSide2} : {potAgainst} WC
</div>
<div className="card">
<form className="form" id="submitWagerForm" autocomplete="off" onSubmit={wager}>
<label>
Enter your wager and side:
<br />
<input
className="input"
type="text"
name="amount"
placeholder="# of WC"
onChange={(t) => setWager(t.target.value)}
/>
<input
className="input"
type="text"
name="side"
placeholder="1 or 2"
onChange={(t) => setUserSide(t.target.value)}
/>
</label>
<button className="button" type="submit" value="Submit">
Submit
</button>
</form>
onSubmit:
const wager = async (t) => {
t.preventDefault();
try{
const accounts = await window.ethereum.enable();
const account = accounts[0];
const _wager = web3.utils.toWei(newWager);
const gas = await eventWagerContract.methods.wager(userSide, _wager).estimateGas();
const post = await eventWagerContract.methods.wager(userSide, _wager).send({ from: account, gas });
getUserWager(t);
getCurrentPot(t);
}
catch(e)
{
alert('Apparently this is the best way to display blockchain errors :/\n\n' + e.message);
}
var form = document.getElementById("submitWagerForm");
form.reset();
};
The rest of App.js
Stripped down from the complete app, but still exhibits the behavior in question
import logo from './logo.svg';
import React, { useState } from "react";
import './App.css';
import { eventWager } from './abi/abi';
import { token } from './abi/abi';
import Web3 from "web3";
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const web3 = new Web3(Web3.givenProvider);
const contractAddress = "0x73A6Da02A8876C3E01017fB960C912dA0a423817";
const tokenAddress = "0x02F682030814F5AE7B1b3d69E8202d5870DF933f";
const eventWagerContract = new web3.eth.Contract(eventWager, contractAddress);
const tokenContract = new web3.eth.Contract(token, tokenAddress);
function App() {
// Getter hooks
const [retrievedWager, setRetrievedWager] = useState(0);
const [currentPot, setRetrievedCurrentPot] = useState(0);
const [potFor, setPotFor] = useState(0);
const [potAgainst, setPotAgainst] = useState(0);
const [retrievedUserSide, setRetrievedUserSide] = useState(0);
const [retrievedSide1, setRetrievedSide1] = useState("1");
const [retrievedSide2, setRetrievedSide2] = useState("2");
const [requestAddress, setRequestAddress] = useState(0);
const [requestAmount, setRequestAmount] = useState(0);
// Setter hooks
const [newWager, setWager] = useState(0);
const [userSide, setUserSide] = useState(0);
const [winningSide, setWinningSide] = useState(0);
// Getter methods
const getUserWager = async (t) => {
if(t) { t.preventDefault(); }
const accounts = await window.ethereum.enable();
const account = accounts[0];
const post = await eventWagerContract.methods.getWager(account).call();
const _wager = web3.utils.fromWei(post);
setRetrievedWager(_wager);
};
const getCurrentPot = async (t) => {
if(t) { t.preventDefault(); }
const post = await eventWagerContract.methods.getPot().call();
const _pot = web3.utils.fromWei(post);
setRetrievedCurrentPot(_pot);
};
// Setter methods
const wager = async (t) => {
t.preventDefault();
try{
const accounts = await window.ethereum.enable();
const account = accounts[0];
const _wager = web3.utils.toWei(newWager);
const gas = await eventWagerContract.methods.wager(userSide, _wager).estimateGas();
const post = await eventWagerContract.methods.wager(userSide, _wager).send({ from: account, gas });
getUserWager(t);
getCurrentPot(t);
}
catch(e)
{
alert('Apparently this is the best way to display blockchain errors :/\n\n' + e.message);
}
var form = document.getElementById("submitWagerForm");
form.reset();
};
const allowSpend = async (t) => {
t.preventDefault();
try{
const accounts = await window.ethereum.enable();
const account = accounts[0];
const gas = tokenContract.methods.approve(contractAddress, web3.utils.toWei('9999')).estimateGas();
const post = tokenContract.methods.approve(contractAddress, web3.utils.toWei('9999')).send({ from: account });
}
catch(e)
{
alert('Apparently this is the best way to display blockchain errors :/\n\n' + e.message);
}
};
const getCurrentSides = async (t) => {
if(t) { t.preventDefault(); }
const post = await eventWagerContract.methods.getSides().call();
// const post = await eventWagerContract.methods.getPot().call();
var substrings = post.split('||&&||');
setRetrievedSide1(substrings[0]);
setRetrievedSide2(substrings[1]);
};
const Home = () => {
return(
<div className="main">
<div className="upper">
1: {retrievedSide1} : {potFor} WC<br/> 2: {retrievedSide2} : {potAgainst} WC
</div>
<div className="card">
<form className="form" id="submitWagerForm" autocomplete="off" onSubmit={wager}>
<label>
Enter your wager and side:
<br />
<input
className="input"
type="text"
name="amount"
placeholder="# of WC"
onChange={(t) => setWager(t.target.value)}
/>
<input
className="input"
type="text"
name="side"
placeholder="1 or 2"
onChange={(t) => setUserSide(t.target.value)}
/>
</label>
<button className="button" type="submit" value="Submit">
Submit
</button>
</form>
<br />
<div>
<button className="button" onClick={getUserWager} type="button">
Your current wager:
</button>
{retrievedWager}
</div>
<br /> <br />
<div>
<button className="button" onClick={getCurrentPot} type="button">
Click for current pot
</button>
{currentPot}
</div>
<br /><br />
<div>
<button className="button" onClick={allowSpend} type="button">
Click to approve
</button>
</div>
<div className="lower">
<form className="form" onSubmit={getCurrentSides}>
<label>
<button className="button" type="submit" value="Submit">
Get Sides
</button>
</label>
</form>
</div>
</div>
</div>
);
}
return (
<Router>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/admin">
{/* <Admin /> */}
</Route>
</Switch>
</Router>
);
}
export default App;
The simpler test app
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
function App() {
const [newWager, setWager] = useState(0);
const [userSide, setUserSide] = useState(0);
const [retrievedSide1, setRetrievedSide1] = useState("1");
const [retrievedSide2, setRetrievedSide2] = useState("2");
const wager = async (t) => {
if(t) { t.preventDefault(); }
else { console.log('No t') };
let str = newWager + ' on side ' + userSide;
wagerComplete(str);
let form = document.getElementById("submitWagerForm");
form.reset();
};
const wagerComplete = async (wagerstr) => {
console.log(wagerstr);
alert(wagerstr);
};
const beginRound = async (t) => {
t.preventDefault();
// setRetrievedSide1(document.getElementById("side1").value);
// setRetrievedSide2(document.getElementById("side2").value);
try{
// const accounts = await window.ethereum.enable();
// const account = accounts[0];
// const gas = await eventWagerContract.methods.beginRound(retrievedSide1, retrievedSide2).estimateGas();
// const post = await eventWagerContract.methods.beginRound(retrievedSide1, retrievedSide2).send({ from: account, gas });
alert(retrievedSide1 + ' ' + retrievedSide2);
wager();
}
catch(e)
{
alert('Apparently this is the best way to display blockchain errors :/\n\n' + e.message);
}
let form = document.getElementById("beginRoundForm");
form.reset();
};
return (
<div className="App">
<form className="form" id="submitWagerForm" autocomplete="off" onSubmit={wager}>
<label>
Enter your wager and side:
<br />
<input
className="input"
type="text"
name="amount"
placeholder="# of WC"
onChange={(t) => setWager(t.target.value)}
/>
<input
className="input"
type="text"
name="side"
placeholder="1 or 2"
onChange={(t) => setUserSide(t.target.value)}
/>
</label>
<button className="button" type="submit" value="Submit">
Submit
</button>
</form>
<form className="form" id="beginRoundForm" autocomplete="off" onSubmit={beginRound}>
<label>
<input
className="input"
type="text"
name="name"
id="side1"
placeholder="Side 1"
onChange={(t) => setRetrievedSide1(t.target.value)}
/>
<input
className="input"
type="text"
name="side"
placeholder="Side 2"
id="side2"
onChange={(t) => setRetrievedSide2(t.target.value)}
/>
<button className="button" type="submit" value="Submit">
Begin Round
</button>
</label>
</form>
</div>
);
}
export default App;
Solution
Looks like I should have spent more time with the docs, I got the behaviour I wanted with useRef.
Answered By - Zach Zimmermann
Answer Checked By - - David Goodson (ReactFix Volunteer)