当 solidity 事件被触发时,我的 React 状态变量在回调中为空
My React state variables are empty in callback when a solidity event gets fired
我正在使用 react
和 truffle/ganache
。我在我的智能合约中定义了一些事件,这些事件会在某些事情发生时触发。我有一个专门用于处理合同实例及其事件的反应组件。这是显示我遇到的问题的精简版...
import React, {useState, useEffect} from "react";
import Client from '../build/contracts/Client.json'
const ClientBugDemo = (props) => {
/**
* props contains...
* contractAddress
* web3
*/
const [contractAddress, setContractAddress] = useState(0);
useEffect(()=>{},[contractAddress]);
const [contractInstance, setContractInstance] = useState(0);
useEffect(()=>{},[contractInstance]);
const [name, setName] = useState("");
useEffect(()=>{},[name]);
function _getContractInstance(address, web3){
var instance = new web3.eth.Contract(
Client.abi,
address,
);
return instance;
}
if (contractAddress === 0){
if (props.contractAddress){
setContractAddress(props.contractAddress);
var instance = _getContractInstance(props.contractAddress, props.web3);
setContractInstance(instance);
instance.events.MyCustomEvent(ev_MyCustomEventHandler);
instance.methods.getName()
.call()
.then((_name, err)=>{
if (err){
console.log("error : ", err);
}
else {
setName(_name);
}
});
}
}
function ev_MyCustomEventHandler(error, event){
console.log("Why is my contract Instance empty? ", contractInstance);
console.log("But my props are fine? ", props.contractAddress);
}
return (
<div>
<div>Contract Address = {contractAddress}</div>
<div>Name = {name}</div>
<div>Have Contract = {contractInstance?"Yes":"No"}</div>
</div>
)
}
export default ClientBugDemo;
我为我的客户合约创建了上述组件之一,并为其提供了一个有效的 contractAddress
和 web3
对象。
它成功加载并显示了合约地址、名称,我可以看到合约实例是有效的。一切都很好,我有一个工作组件。
Contract Address = 0x64cCa2C98c13bB06e7D5b35Ae3af03B4A4C4ec71
Name = Taylor
Have Contract = Yes
然后自定义事件将触发,处理程序 ev_MyCustomEventHandler
将按预期被调用。
问题是在该函数调用期间我的本地 contractAddress
值为零。事实上,据我所知,我在 React 中的所有局部状态变量都处于空状态,就好像组件正在重新渲染一样。然而 props
有其原始值。
所以我的控制台日志显示
Why is my contract Instance empty? 0
But my props are fine? 0x64cCa2C98c13bB06e7D5b35Ae3af03B4A4C4ec71
我尝试了无数种方法,包括重构组件,认为在分配事件处理程序时状态可能会以某种方式持续存在。我真的无法让它工作。
好吧,在 React Hooks 中对闭包等的大量研究使我找到了这个解决方案...
import React, {useState, useEffect} from "react";
import Client from '../build/contracts/Client.json'
const ClientBugDemo = (props) => {
/**
* props contains...
* contractAddress
* web3
*/
const [contractAddress, setContractAddress] = useState(0);
useEffect(()=>{},[contractAddress]);
const [contractInstance, setContractInstance] = useState(0);
useEffect(()=>{},[contractInstance]);
const [name, setName] = useState("");
useEffect(()=>{},[name]);
const [semaphore, setSemaphore] = useState(0);
useEffect(()=>{
console.log("Now my Contract Instance is not empty ", contractInstance);
if (sempahore !== 0){
// I can do what I need to do in response to the event being called
contractInstance.methods.doSomethingFunky()
.call()
.then((_funky, err)=>{
console.log("got something funky ", _funky);
})
// reset the state of the "mutex" so we don't come in here endlessly
setSemaphore(0);
}
},[semaphore, contractInstance]);
function _getContractInstance(address, web3){
var instance = new web3.eth.Contract(
Client.abi,
address,
);
return instance;
}
if (contractAddress === 0){
if (props.contractAddress){
setContractAddress(props.contractAddress);
var instance = _getContractInstance(props.contractAddress, props.web3);
setContractInstance(instance);
instance.events.MyCustomEvent(ev_MyCustomEventHandler);
instance.methods.getName()
.call()
.then((_name, err)=>{
if (err){
console.log("error : ", err);
}
else {
setName(_name);
}
});
}
}
function ev_MyCustomEventHandler(error, event){
if (!error) setSemaphore(event);
}
return (
<div>
<div>Contract Address = {contractAddress}</div>
<div>Name = {name}</div>
<div>Have Contract = {contractInstance?"Yes":"No"}</div>
</div>
)
}
export default ClientBugDemo;
我 认为 正在发生的事情是,我收到的响应 solidity 事件的回调是 运行 作为一个独立的代码片段。 React 不知道它有任何依赖关系。
我的解决方案是添加一种 semaphore
变量,可以在事件到达时对其进行监视。当回调触发时,我将 semaphore
的值设置为包含事件值,然后推迟回 React Hooks。
如果我使用 useEffect
将 semaphore
包含在 useEffect
定义中,从而使 semaphore
依赖于我的 contractInstance
,那么在useEffect
可以访问状态变量并且它有效。
我将继续寻找更优雅的解决方案,因为我承认这感觉像 work-round,但现在这是一个我可以重复的模式,而无需大量重复代码或绝对我所有的代码都嵌入了嵌套的 useEffect
匿名函数中。
我正在使用 react
和 truffle/ganache
。我在我的智能合约中定义了一些事件,这些事件会在某些事情发生时触发。我有一个专门用于处理合同实例及其事件的反应组件。这是显示我遇到的问题的精简版...
import React, {useState, useEffect} from "react";
import Client from '../build/contracts/Client.json'
const ClientBugDemo = (props) => {
/**
* props contains...
* contractAddress
* web3
*/
const [contractAddress, setContractAddress] = useState(0);
useEffect(()=>{},[contractAddress]);
const [contractInstance, setContractInstance] = useState(0);
useEffect(()=>{},[contractInstance]);
const [name, setName] = useState("");
useEffect(()=>{},[name]);
function _getContractInstance(address, web3){
var instance = new web3.eth.Contract(
Client.abi,
address,
);
return instance;
}
if (contractAddress === 0){
if (props.contractAddress){
setContractAddress(props.contractAddress);
var instance = _getContractInstance(props.contractAddress, props.web3);
setContractInstance(instance);
instance.events.MyCustomEvent(ev_MyCustomEventHandler);
instance.methods.getName()
.call()
.then((_name, err)=>{
if (err){
console.log("error : ", err);
}
else {
setName(_name);
}
});
}
}
function ev_MyCustomEventHandler(error, event){
console.log("Why is my contract Instance empty? ", contractInstance);
console.log("But my props are fine? ", props.contractAddress);
}
return (
<div>
<div>Contract Address = {contractAddress}</div>
<div>Name = {name}</div>
<div>Have Contract = {contractInstance?"Yes":"No"}</div>
</div>
)
}
export default ClientBugDemo;
我为我的客户合约创建了上述组件之一,并为其提供了一个有效的 contractAddress
和 web3
对象。
它成功加载并显示了合约地址、名称,我可以看到合约实例是有效的。一切都很好,我有一个工作组件。
Contract Address = 0x64cCa2C98c13bB06e7D5b35Ae3af03B4A4C4ec71
Name = Taylor
Have Contract = Yes
然后自定义事件将触发,处理程序 ev_MyCustomEventHandler
将按预期被调用。
问题是在该函数调用期间我的本地 contractAddress
值为零。事实上,据我所知,我在 React 中的所有局部状态变量都处于空状态,就好像组件正在重新渲染一样。然而 props
有其原始值。
所以我的控制台日志显示
Why is my contract Instance empty? 0
But my props are fine? 0x64cCa2C98c13bB06e7D5b35Ae3af03B4A4C4ec71
我尝试了无数种方法,包括重构组件,认为在分配事件处理程序时状态可能会以某种方式持续存在。我真的无法让它工作。
好吧,在 React Hooks 中对闭包等的大量研究使我找到了这个解决方案...
import React, {useState, useEffect} from "react";
import Client from '../build/contracts/Client.json'
const ClientBugDemo = (props) => {
/**
* props contains...
* contractAddress
* web3
*/
const [contractAddress, setContractAddress] = useState(0);
useEffect(()=>{},[contractAddress]);
const [contractInstance, setContractInstance] = useState(0);
useEffect(()=>{},[contractInstance]);
const [name, setName] = useState("");
useEffect(()=>{},[name]);
const [semaphore, setSemaphore] = useState(0);
useEffect(()=>{
console.log("Now my Contract Instance is not empty ", contractInstance);
if (sempahore !== 0){
// I can do what I need to do in response to the event being called
contractInstance.methods.doSomethingFunky()
.call()
.then((_funky, err)=>{
console.log("got something funky ", _funky);
})
// reset the state of the "mutex" so we don't come in here endlessly
setSemaphore(0);
}
},[semaphore, contractInstance]);
function _getContractInstance(address, web3){
var instance = new web3.eth.Contract(
Client.abi,
address,
);
return instance;
}
if (contractAddress === 0){
if (props.contractAddress){
setContractAddress(props.contractAddress);
var instance = _getContractInstance(props.contractAddress, props.web3);
setContractInstance(instance);
instance.events.MyCustomEvent(ev_MyCustomEventHandler);
instance.methods.getName()
.call()
.then((_name, err)=>{
if (err){
console.log("error : ", err);
}
else {
setName(_name);
}
});
}
}
function ev_MyCustomEventHandler(error, event){
if (!error) setSemaphore(event);
}
return (
<div>
<div>Contract Address = {contractAddress}</div>
<div>Name = {name}</div>
<div>Have Contract = {contractInstance?"Yes":"No"}</div>
</div>
)
}
export default ClientBugDemo;
我 认为 正在发生的事情是,我收到的响应 solidity 事件的回调是 运行 作为一个独立的代码片段。 React 不知道它有任何依赖关系。
我的解决方案是添加一种 semaphore
变量,可以在事件到达时对其进行监视。当回调触发时,我将 semaphore
的值设置为包含事件值,然后推迟回 React Hooks。
如果我使用 useEffect
将 semaphore
包含在 useEffect
定义中,从而使 semaphore
依赖于我的 contractInstance
,那么在useEffect
可以访问状态变量并且它有效。
我将继续寻找更优雅的解决方案,因为我承认这感觉像 work-round,但现在这是一个我可以重复的模式,而无需大量重复代码或绝对我所有的代码都嵌入了嵌套的 useEffect
匿名函数中。