有没有办法在 React 中显示结构的 Solidity 数组?

问题描述

我正在尝试在 React 前端显示 solidity 结构数组的内容

这是我的 solidity 智能合约。我创建了一个函数,它返回有问题的数组的长度。

pragma solidity ^0.8.0;

contract Project {

    struct Person {
        string name;
        string description;
    }
    
    Person[] public people;

    function getPersonCount() public view returns (uint) {
        return people.length;
    }

}

这是我的前端 React 代码

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {

    let personCount = 0;

    personCount = ContractInstance.methods.getPersonCount().call();

    return (
        personCount
    );
};

export default NewPerson;

当我运行该前端代码时,我收到此错误消息:

错误:对象作为 React 子对象无效(找到:[object Promise])。如果您打算渲染一组子项,请改用数组。

阅读这里问题的答案,我认为问题可能是我需要异步调用我的 solidity 函数,以便我可以返回函数输出而不是承诺。我试着像这样重写我的 React 代码

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {

    let personCount = 0;

    async function handlePerson() {
        personCount = await ContractInstance.methods.getPersonCount().call();
    }

    handlePerson();

    return (
        personCount
    );
};

export default NewPerson;

这并没有触发错误,而是返回了 0(表明 handlePerson 函数甚至没有运行)。

然后我尝试了另一种方法

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {


    let personCount = 0;

    async function handlePerson() {
        personCount = await ContractInstance.methods.getPersonCount().call();
        
        return (
            personCount
        );
    }
    return (
        handlePerson()
    );

};

export default NewPerson;

这给了我第一次收到的相同错误消息:

错误:对象作为 React 子对象无效(找到:[object Promise])。如果您打算渲染一组子项,请改用数组。

如果有人可以提供任何建议,或者如果有人经历过类似的事情,我将不胜感激。我的目标是然后遍历数组以显示其所有元素,但到目前为止我什至似乎无法显示其中的元素数量。这特别奇怪,因为我能够通过 React 前端成功地从我的智能合约调用其他函数。非常感谢!

更新:

非常感谢 MrFrenzoid 的帮助。我重新编写了我的前端代码,以便我现在能够查询 solidity 数组的各个元素:

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {
    
    // Using hard-coded personCount for testing purposes
    let personCount = 70;
    let people = [];

    async function handlePeople() {
        for (let i=0; i<personCount; i++) {
            const person = await ContractInstance.methods.people(i).call();
            people.push(person);
        }
        console.log(people);
    }

    handlePeople();

    return (
        null
    );

};

export default NewPerson;

正如预期的那样,这将在控制台中返回数组的内容。 我仍然遇到的问题是查询 solidity 数组的长度,以便我可以使用它来计算 personCount(而不是像上面那样使用硬编码值)。

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {

    let people = [];

    async function handlePersonCount() {
        const personCount = await ContractInstance.methods.getPersonCount().call();
        console.log(personCount);
    }

    async function handlePeople() {
        for (let i=0; i<personCount; i++) {
            const person = await ContractInstance.methods.people(i).call();
            people.push(person);
        }
        console.log(people);
    }

    handlePersonCount();
    handlePeople();

    return (
        null
    );

};

export default NewPerson;

当我运行 handlePersonCount() 时,我收到此错误消息:

error message

我之所以采用这种方法,是因为我似乎无法一次查询整个 solidity 数组,而是需要一次查询一个元素。

更新:

在我重新部署运行本地区块链的 Truffle/Ganache 实例后,我能够按预期从前端调用 getPersonCount()。我的错误似乎是在智能合约中创建新函数/变量后没有重新部署我的本地区块链。

这是我最终的、正常运行的前端代码的样子:

import React from "react";
import Web3 from './web3';
import { ABI } from './ABI';
import { contractAddr } from './Address';

const web3 = new Web3(Web3.givenProvider);
const ContractInstance = new web3.eth.Contract(ABI,contractAddr);

const NewPerson = () => {

    let people = [];

    async function handlePersonCount() {
        const personCount = await ContractInstance.methods.getPersonCount().call();
        console.log(personCount);
        return(personCount);
    }

    async function handlePeople(qty) {
        for (let i=0; i<qty; i++) {
            const person = await ContractInstance.methods.people(i).call();
            people.push(person);
        }
        console.log(people);
    }

    async function handler() {
        await handlePeople(await handlePersonCount());
    }

    handler();

    return (
        null
    );

};

export default NewPerson;

我在handlePersonCount()中添加了返回行,以便我可以将handlePersonCount()作为参数输入handlePeople(),然后使用handler()函数强制handlePeople()等待,直到hanldePersonCount()完成之前被调用。可能有一种更简洁的方法来设置该顺序功能,但这似乎有效。

解决方法

为了回应您的最后一条评论,由于您已经在 js 中拥有数组,您可以快速执行 people.length 您也可以使用普通 for 对其进行迭代,

或一个

people.foreach((person) => { console.log(person); });

for( const person in people){
   console.log(person);
}

如果您有任何问题,请告诉我,请在下方附上错误和您的代码。

更新 1(如何使用 web3 获取动态数组)。

试试这个: 创建一个状态变量来保存您的 peoples 数组,在定义时将其设为空数组。

然后,执行以下操作以迭代每个元素,并将它们堆叠起来。

// we create a local variable with the same name as the state variable where were going to hold our people elements.
let peopleArray = [];

// we query our counter.
const peopleCount = ContractInstance.methods.peopleCount().call();

// we iterate over each people and add it to our local variable.
// I start at 1 since in my contracts i first increment the counter
//   and then do else,since the contract calls aren't mutex,//   theres a chance for users to be able to trigger a "race condition" by
//   adding two "people" very fast,so fast that since the counter 
//   didn't update the first people was added,the second one will be 
//   processed with the same value as its counter,thats why,try
//   counting first,and then doing anything else,its a good practice! :D.

for (var i = 1; i <= peopleCount ; i++) {
        const person = ContractInstance.methods.people(i).call();
        console.log(person);
        peopleArray.push(person);
  }

// we set the local varible value as our state variable,since it has the same name,you dont need to do {peopleArray: peopleArray}
this.setState({peopleArray});

您的合同应如下所示:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Project {

struct Person {
    string name;
    string description;
}

Person[] public people;
uint256 public peopleCount;

constructor(){
    peopleCount = 0;
}

function addPerson(string memory name,string memory description) public returns (Person memory) {
    // we do our corresponding checks.
    require(bytes(name).length > 0,"Error: Dont leave the name empty!");
    require(bytes(description).length > 0,"Error: Dont leave the description empty!");

    // We increment the counter first so we avoid a race condition
    peopleCount++;
    
    // Create a person
    Person memory p = Person(name,description);
    
    // Push the person
    people[peopleCount] = p;
    
    // return the created person.
    return p;
}

}

请记住,将存储变量(诸如people 和peopleCount 的合同全局变量)设置为public,将使solidity 隐式地为每个变量创建getter,因此如果您将变量设置为public,请不要费心创建getter,除非您想要什么在返回所述数据之前要做的事情,例如,跟踪人们访问所述变量的次数。

最好的问候!