Logan Pederson
Logan Pederson

Reputation: 23

React-Table not displaying variable data returned from API, verified API returning expected data

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

Answers (2)

Logan Pederson
Logan Pederson

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

Timothy
Timothy

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

Related Questions