0

I have picked up a React project that uses Polkadot.js from another developer and I'm struggling to understand how to achieve the following:

I want to display a grid of images. The initial part of the image URL is hardcoded in a const.

When the user clicks on a button, the user's account is checked asynchronously to see what NFT's they own. However there isn't an array/object what I need, so I have to create an array using the hardcoded URL, along with the provided number and add the image file extension at the end of the string. eg. imageURL + imageNumber + '.png'

The async function that is called on click is as follows:

const getNFT = async (address: string) =>  {
    const wsProvider = new WsProvider(wsUrl);
    const api = await ApiPromise.create({ provider: wsProvider });

    // NOTE the apps UI specified these in mega units -> https://polkadot.js.org/docs/api-contract/start/contract.read
    // @ts-ignore
    const gasLimit = -1;
    // Read from the contract via an RPC call
    const value = 0; // only useful on isPayable messages -> https://polkadot.js.org/docs/api-contract/start/contract.read

    // @ts-ignore
    const contract = new ContractPromise(api, abi, contractAddress);

    const {gasConsumed, result, output} = await contract.query['psp34::balanceOf'](address, {value: 0, gasLimit: -1}, address);
    console.log(JSON.stringify(result.toHuman()));
    if (result.isOk) {
      // should output 123 as per our initial set (output here is an i32)
      // @ts-ignore
      setUserNFTCount(output.toHuman());

      // @ts-ignore
      console.log('Number of NFT you own:', output.toHuman());

      // @ts-ignore
      for (let i = 0; i <= output.toNumber() - 1; i++) {
        console.log ("Getting NFT at index " + i);
        const NFTIndexResult = await contract.query['psp34Enumerable::ownersTokenByIndex'](address, { value: 0, gasLimit: -1 }, address, i);
        
        if (NFTIndexResult.result.isOk && NFTIndexResult.output != null) {
          console.log(NFTIndexResult);
          console.log(NFTIndexResult.output.toHuman())
           // @ts-ignore
          var pNumber = NFTIndexResult.output.toHuman().Ok.U64;
          console.log('NFT number you own:' + pNumber);

          var metadata = metadataUrl + pNumber + '.json';
          var image = imageUrl + pNumber + '.png';

          console.log('TODO, show image: ' + image);
          let myImageArray = [];
          myImageArray.push(image)
          console.log('array = ' + myImageArray);
        }
      }

    } else {
      console.error('Error', result.asErr);
    }
  }

When I check my console, I can see the individual images I want to add to an array within the image variable. But I'm not sure how to add each image into a single array which I can then use to render a grid of the images.

Edit - For further context: When I do a console log, I only see each individual image path per line rather than an array, even when moving the console log outside of the loop eg. (www.somewebsite.com/image1.png or www.somewebsite.com/image2.png)

ImranR
  • 516
  • 2
  • 13
  • 30

2 Answers2

1

Declare the array outside the loop so it isn't declaring a new array each iteration.

Example:

const myImageArray = []; // <-- declare prior to loop

// @ts-ignore
for (let i = 0; i <= output.toNumber() - 1; i++) {
  console.log ("Getting NFT at index " + i);
  const NFTIndexResult = await contract.
    query['psp34Enumerable::ownersTokenByIndex'](
      address,
      { value: 0, gasLimit: -1 },
      address,
      i,
    );
    
  if (NFTIndexResult.result.isOk && NFTIndexResult.output != null) {
    console.log(NFTIndexResult);
    console.log(NFTIndexResult.output.toHuman());
    // @ts-ignore
    var pNumber = NFTIndexResult.output.toHuman().Ok.U64;
    console.log('NFT number you own:' + pNumber);

    var metadata = metadataUrl + pNumber + '.json';
    var image = imageUrl + pNumber + '.png';

    console.log('TODO, show image: ' + image);
      
    myImageArray.push(image); // <-- push all into same array
    console.log('array = ' + myImageArray);
  }
}

Presumably you'll be doing something with myImageArray after the loop.

Update using array of Promise objects

You might find it easier to collect the fetched images using an array of Promise objects.

Example:

const promises = Array.from({ length: output.toNumber()})
  .map((_, i) => {
    return contract.
      query['psp34Enumerable::ownersTokenByIndex'](
        address,
        { value: 0, gasLimit: -1 },
        address,
        i,
      )
      .then(NFTIndexResult => {
        if (NFTIndexResult.result.isOk && NFTIndexResult.output != null) {
          console.log(NFTIndexResult);
          console.log(NFTIndexResult.output.toHuman());
          // @ts-ignore
          const pNumber = NFTIndexResult.output.toHuman().Ok.U64;
          console.log('NFT number you own:' + pNumber);

          const metadata = metadataUrl + pNumber + '.json';
          const image = imageUrl + pNumber + '.png';

          console.log('TODO, show image: ' + image);
      
          return image;
        }
      });
  });

const myImageArray = await Promise.all(promises);

console.log('array = ' + myImageArray);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thanks, I'm not sure why I only get 1 image when doing a console log (just as a string). I was expecting to see an array with multiple images within it. – ImranR Jul 31 '22 at 23:07
  • @ImranR Are you referring specifically to the `console.log('array = ' + myImageArray);` inside the loop? What & where are you console logging? – Drew Reese Jul 31 '22 at 23:10
  • Yes that's the one. I tried to console log in multiple places, if I do one after the loop, I only get the last image. If I do it within the loop, I get all the images individually in their own lines. – ImranR Jul 31 '22 at 23:13
  • @ImranR Interesting. I'd expect to see `output.toNumber() - 1` console logs and the `myImageArray` possibly built up in all of them. You might want to try an implementation using an array of Promises instead. I'll update with an example. – Drew Reese Jul 31 '22 at 23:17
  • Sorry for late response, and thanks again for your help - the updated example you provided returns all the image paths I want which is great, but not as an array. For example the final log gives me the following: array = https://www.website.com/1.jpg,https://www.website.com/2.jpg,https://www.website.com/3.jpg How would I put them into an array as I want to map through the array to create a grid of images using the image paths. – ImranR Aug 16 '22 at 20:18
  • I think I figured it out using Object.values(myImageArray), or Object.entries(myImageArray) – ImranR Aug 16 '22 at 20:25
  • @ImranR No worries. Sounds like you've got your question/issue all resolved now? – Drew Reese Aug 16 '22 at 20:41
  • Kind of, I'm trying to figure out how I can adjust the scope so that I can access and map the new array, but I'll create a new question for it – ImranR Aug 16 '22 at 20:43
  • @ImranR OK, if you like, feel free to ping me here with a link to any new post and I can take a look when available. – Drew Reese Aug 16 '22 at 20:48
0

It seems your issue is that of scope. I assume you are looking to have the array of image url's outside of the async function:

const getNFT = async (address: string) => 

I see you declared a local variable myImageArray inside the for loop. This will cause a number of issues. It, at minimum, needs to be outside the for loop so you don't override your variable with a new empty array, erasing all your data.

There are a number of approaches to this problem, but probably the best would be to collect your results, similar to your current approach, but then return the values from the function. See the below example:

const myImageArray = []; // Declare your array here so if you fail to retrieve you are returning an empty array (if that is your desired behavior)

console.log(JSON.stringify(result.toHuman()));
if (result.isOk) {
    // should output 123 as per our initial set (output here is an i32)
    // @ts-ignore
    setUserNFTCount(output.toHuman());

    // @ts-ignore
    console.log('Number of NFT you own:', output.toHuman());

    // @ts-ignore

    for (let i = 0; i <= output.toNumber() - 1; i++) {
        console.log("Getting NFT at index " + i);
        const NFTIndexResult = await contract.query['psp34Enumerable::ownersTokenByIndex'](address, { value: 0, gasLimit: -1 }, address, i);

        if (NFTIndexResult.result.isOk && NFTIndexResult.output != null) {
  
            // removed for brevity
            var image = imageUrl + pNumber + '.png'
            myImageArray.push(image);
        }
    }

} else {
    console.error('Error', result.asErr);
}
return myImageArray; // return the array, as that is the data you seem to want out of the function

Then you can use your function elsewhere like so:

const imageArray = await getNFT('someAddress')
nklisch
  • 76
  • 3
  • Thanks for the explanation - Unfortunately this yields the same results as what I was getting. When I do a console .log outside of the loop, it still only shows 1 string (the image path), but I'm expecting an array of image paths. – ImranR Jul 31 '22 at 23:05