I am trying to display the contents of a Solidity array of structs in a React front-end.
This is my Solidity smart contract. I've created a function which returns the length of the array in question.
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;
}
}
Here is my front-end React code:
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;
When I run that front-end code, I get this error message:
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
Reading answers to questions on here, I thought the problem might be that I need to call my Solidity function asynchronously so that I can return the output of the function rather than a promise. I tried re-writing my React code like so:
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;
This did not trigger an error, but instead returned 0 (suggesting that the handlePerson function did not even run).
I then tried another approach:
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;
This gave me the same error message I got the first time around:
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I'd be grateful if anyone has any advice to offer, or if anyone has experienced something similar. My goal is to then iterate through the array to display all of its elements, but so far I don't even seem to be able to display the number of elements in it. It's particularly strange since I'm successfully able to call other functions from my smart contract via the React front-end. Many thanks in advance!
Update:
Many thanks to MrFrenzoid for the help. I've re-written my front-end code so that I am now able to query individual elements of the Solidity array:
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;
This returns the contents of the array in the console, as expected. The issue I'm still having is querying the length of the Solidity array, so that I can use that to compute personCount (rather than using a hard-coded value as I do above).
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;
When I run handlePersonCount(), I get this error message: 
The reason I'm taking this approach at all is because I'm seemingly unable to query the entire Solidity array at once, and instead need to query one element at a time.
Update:
Once I re-deployed the Truffle/Ganache instance in which I was running my local blockchain, I was able to invoke getPersonCount() from the front-end as expected. It seems my error was in not redeploying my local blockchain after creating a new function/variable in the smart contract.
Here is what my final, functioning front-end code looks like:
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;
I added the return line to handlePersonCount() so that I can feed handlePersonCount() into handlePeople() as a parameter, and then use the handler() function to force handlePeople() to wait until hanldePersonCount() has finished before being invoked. There's probably a cleaner way to set up that sequential functionality, but this seems to work.