I have a simple component that displays a table using react-table. With the mock data set as default with useState it renders fine, and on calling the api using variable data from the text boxes I get status 200 and the proper data returned. The issue I am having is my useTable section of code is running as soon as my useState which contains the api returned DATA runs. useState is async, so at that point is returning undefined as a not yet resolved promise. I read the solution to doing something only after useState is fully resolved would be to use useEffect, however since useTable is a hook I cannot have a hook within a hook, what the heck do I do!
Code:
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(true);
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]);
useEffect (() => {
console.log('data with useEffect: '+JSON.stringify(data))
}, [data]);
useEffect (() => {
console.log('DATA with useEffect: '+JSON.stringify(DATA))
}, [DATA]);
useEffect (() => {
ToggleShown();
},[])
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;
console.log('arr = '+arr)
console.log('arr stringified = '+JSON.stringify(arr))
setDATA(arr)
return (arr)
} catch(e) {
console.log(e)
}
}
const ToggleShown = () => {
setShown(!shown)
}
const HandleClick = () => {
populateTable().then (() => {
console.log('this happened after pouplateTable completed the promise')
})
}
const whatIsSetShown = async () => {
ToggleShown();
console.log('Stringified data: '+String(JSON.stringify(data)))
console.log('data: '+String(data))
console.log('DATA = '+DATA)
console.log('DATA stringified = '+JSON.stringify(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 && 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}
</div>
)
};
Notice this section here is the trouble right now:
const tableInstance = useTable({
columns,
data
});
How can I only have this run once my useState DATA is resolved, as the const data is set from this using useMemo taking DATA as a dependency which seems to work only running once DATA resolves to a new value and then useMemo updates. But I cannot memoize nor useEffect the tableInstance constance as it's a hook itself!
Error I get upon running:
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:46: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 performConcurrentWorkOnRoot (react-dom.development.js:25738:1)
at workLoop (scheduler.development.js:266:1)
at flushWork (scheduler.development.js:239:1)
at performWorkUntilDeadline (scheduler.development.js:533:1)
at run (setImmediate.js:40:1)
at runIfPresent (setImmediate.js:69:1)
at onGlobalMessage (setImmediate.js:109:1)