reactjsarraysfunctionblockchainipfs

How to render dynamic data (from blockchain & IPFS) on react table component?


Frontend : React(Beginner in react) Backend : Blockchain,IPFS,Node js

used IPFS to store details of an application(large data) and saved its hash in blockchain ( smart contracts) for a user and list of application number for a user gets stored in blockchain in an array.

Rendering application details from backend(from blockchain and IPFS) using an array in react,but there are issues rendering dynamic data. Some data is missing while showing on table component in react or after reloading page few times complete data fetched and shown on table component. Used multiple functions to fetch data.

Dynamic data viewed on table but data viewed on table component show more or less on reloading page.

import { useState, useEffect } from "react";
import {  useLoaderData,  Link } from "react-router-dom";
import Table from "../components/Table";
import { Buffer } from 'buffer';

import getHashFromIPFS from "../services/getHashFromIPFS";

let applicationContract, metamaskAddress;
let applicationNumberArray;
function ViewApplication() {//Functional Component
  const { fetchBlockchainData } = useLoaderData();  //Fetch metamask wallet and contracts
  const [dataTable, setDataTable] = useState([]); //array to set dynamic data 
  metamaskAddress = fetchBlockchainData[0];
  applicationContract = fetchBlockchainData[2];

  useEffect(() => {
    fetchData() // Function 1
      .then((res) => {
        console.log('res')
        console.log(res)
      })
      .catch(err => console.log(err))
  }, [])

  const fetchData = async () => {
    let appArray = []
    let listOfApplication = []
    // Step 1 : Fetch list of application(application number) of a user from Blockchain 
    applicationNumberArray = await applicationContract.methods.getUserApplication(metamaskAddress).call();
    // Step 2 : Fetch application details(ipfs hash) for each application number
    applicationNumberArray.map(async (applicationNumber) => {
      await getApplicationDetails(applicationNumber) // Function 2
        .then((result) => {
          //tried to set data here but react component re-render in loop
          // setDataTable(result) 
          listOfApplication.push(result)
        })
    })
    setTimeout(() => {
      //Step 5: Set state variable each time data returned
      setDataTable(listOfApplication)
    }, 3000)
  }
    const getApplicationDetails = (appno) => {
      return new Promise(async (res, rej) => {
        //Step 3 : Get Ipfs hash for each application from Blockchain
        await applicationContract.methods.getApplicationData(appno).call().then(async (AppDataHash) => {
          //Step 4 : Extract ipfs hash and return its data 
          await getHashFromIPFS(AppDataHash).then((getData) => {
            let ipfsConverted = JSON.parse(Buffer.from(getData.value.buffer).toString());
            let application = [];
            application = ipfsConverted;
            res(application);
          });
        });
      });

    }
    //Step 6 Set dynamic data to reusable table component 
    let data = dataTable;
    if (data.length > 0) {
      const config = [
        { label: 'Application No.', render: (data) => data.applicationno },
        { label: 'Name', render: (data) => data.name, },
        { label: 'Status', render: (data) => data.status, },
        { label: '', render: (data) => <><button>View More</button></> }
      ];
      const keyFn = (data) => {
        return data.applicationno;
      };
      return (
        <>
          <Table data={data} config={config} keyFn={keyFn} />
        </>
      )
    }
  }

Solution

  • You aren't waiting for all of the promises to finish.

    You can simplify your code by hoisting the data-fetching outside of the component itself (and by using async/await like you should instead of the new Promise antipattern):

    async function getApplicationDetails(applicationContract, appno) {
      const appDataHash = await applicationContract.methods.getApplicationData(appno).call();
      const ipfsData = await getHashFromIPFS(appDataHash);
      return JSON.parse(Buffer.from(ipfsData.value.buffer).toString());
    }
    
    async function getApplicationsData(applicationContract, metamaskAddress) {
      const applicationNumberArray = await applicationContract.methods.getUserApplication(metamaskAddress).call();
      // Fires off the promises...
      const applicationPromises = applicationNumberArray.map((appNo) => getApplicationDetails(applicationContract, appNo));
      // ... waits for them to finish and gathers the data.
      return await Promise.all(applicationPromises);
    }
    
    function ViewApplication() {
      const { fetchBlockchainData } = useLoaderData();
      const [dataTable, setDataTable] = useState(null);
      const metamaskAddress = fetchBlockchainData[0];
      const applicationContract = fetchBlockchainData[2];
    
      useEffect(() => {
        if (!(metamaskAddress && applicationContract)) return;
        getApplicationsData(applicationContract, metamaskAddress)
          .then((res) => setDataTable(res))
          .catch((err) => console.log(err));
      }, [applicationContract, metamaskAddress]);
    
      if (dataTable === null) return <div>Loading...</div>;
    
      // ... render using dataTable...
    }