0

I am using ReactJS, Ethers.js, and solidity.

Here, I'm using a useEffect to retrieve data from a smart contract (using ethers.js). This works fine.

    const createAddressArray = async () => {
      const addressArr = await contract.getArray()
      setAddressArray(addressArr);
      const cArr = [];
      addressArr.map( async (a) => {
        const data = await contract.getSample(a)
        const lat = parseFloat(data.latitude);
        const lng = parseFloat(data.longitude);
        const l = parseInt(data.somethingelse.toHexString(), 16);
        const message = data.message;
        const address = data.sampleAddress;

        cArr.push([lat, lng, l, address, message])
      })
      setCirclesArr(cArr);
      console.log(cArr) // everything here works fine
  };
    createAddressArray()
  }, [])

However, when I call this in the html section, it doesn't work. Perhaps it's because it's running before the circlesArr state is set, but once that's set it should have rerendered right? Apparently not.

<div>
    {circlesArr}
    {circlesArr.map(() => {
        console.log('mapping')
    })}
</div>

The circlesArr does not show up in the div, and "mapping" does not show up in the console, suggesting that the array is empty when this runs. How do I make this run again when the array is updated? Thanks in advance!

zz uu
  • 3
  • 2
  • 2
    Your async in `Array.prototype.map` is the problematic part: your `setCirclesArr()` is invoked immediately after `map` is invoked, which internally does not wait for all entries to be pushed. Use a `for` loop instead, or map to an array of Promises and use `Promise.all()` before invoking `setCirclesArr()` – Terry Jul 30 '22 at 21:31
  • Consider also using `forEach` instead of `map` if you are not planning to use `map` as intended – Konrad Jul 30 '22 at 21:34
  • 1
    `Array.prototype.forEach` will still lead to the same problem. – Terry Jul 30 '22 at 21:36
  • @Terry is right about this. Here is a discussion about same topic https://stackoverflow.com/a/37576787/10623634 – Bikram Karki Jul 30 '22 at 21:42
  • Does this answer your question? [Best way to call an asynchronous function within map?](https://stackoverflow.com/questions/33438158/best-way-to-call-an-asynchronous-function-within-map) – pilchard Jul 30 '22 at 21:53
  • @Terry I have tried putting this in the html. {for (const a in circlesArr) { console.log('hi') }} But it appears that react doesn't work that way. How would I insert that for loop? – zz uu Jul 30 '22 at 21:55
  • @pilchard could you please demonstrate how to wrap my function into a promise? I'm a javascript beginner, so I would really appreciate that. Thanks! – zz uu Jul 30 '22 at 22:04

1 Answers1

0

Use Array.prototype.map to return an array of promises, which is then resolved using Promise.all. There are a few ways to write this by mixing async-await and .then but this is a clean and efficient way to do it imo.

      const cArr = await Promise.all(
        addressArr.map( (a) => 
          contract.getSample(a).then(data => {
            const lat = parseFloat(data.latitude);
            const lng = parseFloat(data.longitude);
            const l = parseInt(data.somethingelse.toHexString(), 16);
            const message = data.message;
            const address = data.sampleAddress;

            return [lat, lng, l, address, message]
          })
        )
      )
      setCirclesArr(cArr);
wxker
  • 1,841
  • 1
  • 6
  • 16
  • When I did this and console logged cArr, the contents of the array were all undefined. Am I doing something wrong? – zz uu Jul 31 '22 at 17:56
  • The result of `contract.getSample(a)` may not be what you expected. Try logging `data` to see if `data.latitude`, `data.longitude`, etc. is the right way to extract the values. – wxker Aug 01 '22 at 15:14