1

I have a react component that calls to my API and the API returns data. I display that data in a react-table, using mock data works fine. Console.log verifies I am getting the returned data I expect. However I continue to fail to have the page display the react-table responsively from text input and I hope you can assist me in understanding what the problem is. Originally I was using useState without realizing it is an async function, I expect my issue lies somewhere in the misunderstanding of this and how the DATA is being handled. DATA is the response from the api, it has to be formatted in a way react-table likes.

app.js

import { CallApi } from "./Component/callApi";
import React from 'react'

function App() {
  return (
    <div>
      <CallApi />
    </div>
  );
}

export default App;

callApi.js

import React, { useState, useEffect, useMemo } from "react";
import { COLUMNS } from "./columns";
import { useAsyncDebounce, useTable } from "react-table";
import axios from 'axios'



export const CallApi = () => {

const [buildingName, setBuildingName] = useState('');
const [buildingLevel, setBuildingLevel] = useState('');
const [productionModifierPercentage, setProductionModifier] = useState('');
const [adminCostPercentage, setAdminCostPercentage] = useState('');
const [phase, setPhase] = useState('');
const [result, setResult] = useState(null);
const [DATA, setDATA] = useState([{
  "1": {
    "itemName": "Energy research",
    "profitPerHour": 821.6746356364301
  }
}]);

const [shown, setShown] = useState(false);

const columns = useMemo(() => COLUMNS, []);
// const data = useMemo(() => { // Because our nested dictionary returned from fastAPI is an array of one element object, which contains more objects we need to transform this into an array using .map
//   const rawData = DATA[0];
//   if (rawData != undefined && rawData != null){
//     console.log('rawData is no longer undefined or null, now is: '+String(rawData))
//   return Object.keys(rawData).map((key) => rawData[key]);
//   }
// }, [DATA]);
if (DATA[0] != undefined && DATA[0] != null){
  var data = Object.keys(DATA[0]).map((key) => DATA[0][key]);
}

useEffect (() => {
  var data = Object.keys(DATA[0]).map((key) => DATA[0][key]);
}, [DATA]);

const tableInstance = useTable({
    columns,
    data
});

const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow
  } = tableInstance;

const populateTable = async () => {
    try{
        let encodedName = encodeURI(buildingName)
        let targetURI = `http://localhost:8000/api/calculateProfitPerHourOf{}?buildingName=${encodedName}&buildingLevel=${buildingLevel}&productionModifierPercentage=${productionModifierPercentage}&administrationCostPercentage=${adminCostPercentage}`
        let res = await axios.get(targetURI);
        let arr = res.data;
        return (arr)
        
    } catch(e) {
        console.log(e)
    }
}

useEffect (() => {
  console.log('DATA with useEffect: '+JSON.stringify(DATA))
}, [DATA]);


const ToggleShown = () => {
  setShown(!shown)
}

const HandleClick = () => {
  setDATA(populateTable());
  ToggleShown();
}
const whatIsSetShown = async () => {
    console.log('Stringified DATA: '+String(JSON.stringify(DATA)))
    console.log('data: '+String(data))
}



return(
    <div>
        <input type="text" name="buildingName" id="buildingName" placeholder="Building Name" onChange={e => setBuildingName(e.target.value)}></input>
        <input type="text" name="buildingLevel" id="buildingLevel" placeholder="Building Level" onChange={e => setBuildingLevel(e.target.value)}></input>
        <input type="text" name="productionModifier" id="productionModifier" placeholder="Production Modifier %" onChange={e => setProductionModifier(e.target.value)}></input>
        <input type="text" name="adminCost" id="adminCost" placeholder="Administration Cost %" onChange={e => setAdminCostPercentage(e.target.value)}></input>
        <input type="text" name="phase" id="phase" placeholder="Phase"></input>
        <p id='Button'>
        <button type='button' id="getDBinfo" className='block' onClick={()=>HandleClick()}>Get DB Info</button>
        <button type='button' id="getDBinfo" className='block' onClick={()=>whatIsSetShown()}>Print State of setShown</button>
        
        </p> 
        {shown?
          <table {...getTableProps()}>
            <thead>
              {headerGroups.map((headerGroup) => (
                <tr {...headerGroup.getHeaderGroupProps()}>
                  {headerGroup.headers.map((column) => (
                    <th {...column.getHeaderProps()}>{column.render("Header")}</th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody {...getTableBodyProps()}>
              {rows.map((row) => {
                prepareRow(row);
                return (
                  <tr {...row.getRowProps()}>
                    {row.cells.map((cell) => {
                      return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
          :null}
    </div> 

) 

};

It's a bit messy right now, when searching for similar questions on stack overflow I found many were as I was expecting useState to be able to console.log right after setting it, but due to being a async function it will not be able to. Thus many suggested creating a useEffect function that takes the useState variable I want to see the new value of as a dependency. My problem seems to be when I update the DATA useEffect Variable it causes things to break, I think here:

const tableInstance = useTable({ columns, data }); But why? I cannot use useEffect to make this wait for DATA to be set as it's a hook itself and cannot have hooks inside of hooks. I don't understand if it's not yet set the new value of DATA, why it doesn't just render with the mockData set as the default for DATA.

The error I get when running with the button that pulls new DATA from api is:

useTable.js:591 Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')
    at accessRowsForColumn (useTable.js:591:1)
    at useTable.js:195:1
    at updateMemo (react-dom.development.js:17246:1)
    at Object.useMemo (react-dom.development.js:17886:1)
    at Object.useMemo (react.development.js:1650:1)
    at useTable (useTable.js:57:1)
    at CallApi (callApi.js:41:1)
    at renderWithHooks (react-dom.development.js:16305:1)
    at updateFunctionComponent (react-dom.development.js:19588:1)
    at beginWork (react-dom.development.js:21601:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at beginWork$1 (react-dom.development.js:27451:1)
    at performUnitOfWork (react-dom.development.js:26557:1)
    at workLoopSync (react-dom.development.js:26466:1)
    at renderRootSync (react-dom.development.js:26434:1)
    at performSyncWorkOnRoot (react-dom.development.js:26085:1)
    at flushSyncCallbacks (react-dom.development.js:12042:1)
    at react-dom.development.js:25651:1

Line 41 it refers to is the useTable const

2 Answers 2

0

Add condition into rendering of table. Method populateTable is async and renders after first rendering.

    {(shown && DATA) ?
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map((headerGroup) => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <th {...column.getHeaderProps()}>{column.render("Header")}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          {rows.map((row) => {
            prepareRow(row);
            return (
              <tr {...row.getRowProps()}>
                {row.cells.map((cell) => {
                  return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    :null}
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your time, I am excited to get off work and try this!
This prevents the errors I was getting on calling the function to populate table, however I am still having the same issue of useState being async and the useTable function is running while the async function/promise*? is not yet resolved. I read the solution to doing something only after useState is set is using useEffect, but as useTable is a hook I cannot have it within a useEffect hook that only runs on change of DATA. How the heck do I get around this so that useTable only runs once the promise for DATA resolves on update?
Hook useTable takes two parameters: columns and data. Where's data variable is defined? Inside useEffect you define a local variable
The issue was truly that data was undefined, the data returned from the API was not actually requiring mapping over DATA[0], at some point it was an array containing a single object at DATA[0] which was itself an explicitly numbered array starting from 1 instead of 0, a mess of my own creation. Switching back to useMemo for data and setting rawData = DATA instead of DATA[0] solved my problems! Thanks for the help
0

The issue was truly that data was undefined, the data returned from the API was not actually requiring mapping over DATA[0], at some point it was an array containing a single object at DATA[0] which was itself an explicitly numbered array starting from 1 instead of 0, a mess of my own creation. Switching back to useMemo for data and setting rawData = DATA instead of DATA[0] solved my problems!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.