Reputation: 23
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
Upvotes: 1
Views: 861
Reputation: 23
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!
Upvotes: 0
Reputation: 649
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}
Upvotes: 0