0
votes

I'm new to React Hooks and I'm unable to fix a State issue. I've read elsewhere on Stack Overflow about the asynchronous nature of useState/useEffect and it seems that whatever I try, including async/await, I cannot assign the JSON array results to my [data, callback] to render elements of the returned data in a simple table in my main App(.js) - see below. I've got an App.js, a Hooks.js and a Tradetable.js where the table of elements is populated (and the latter two files are imported inside App.js).

App.js

import React, { useState, Fragment } from 'react'
import AddTradeForm from './forms/AddTradeForm'
import EditTradeForm from './forms/EditTradeForm'
import TradeTable from './tables/TradeTable'
import { useFetch } from './Hooks'

const App = () => {
    // get Data from API call
    const URL = "http://192.168.1.1:3000/api/queryMyCommodity"; //
    const tradeData = useFetch(URL, {username:'john', channel:'channel1', name: 'GOLD'}); // the result of this `axios` call inside `useFetch` (in Hooks.js below), is exactly the same as the commented `const` line below - which would work just fine for table item rendering, ie when testing with a JSON array of 3 table items.

// const tradeData = [{"class":"demoCommodity","description":"really fabulous gold","fcn":"demoCreate","id":"trade001","name":"GOLD","owner":"JOHAN.BRINKS","tradename":"GLD"},{"class":"org.example.trading.Commodity","description":"more fabulous gold","fcn":"demoCreate","id":"trade004","name":"GOLD","owner":"NED.SLIVOVITZ","tradename":"GLD"},{"class":"org.example.trading.Commodity","description":"absolutely fabulous gold","fcn":"demoCreate","id":"trade005","name":"GOLD","owner":"ED.GOLDBERG","tradename":"GLD"}]

    // Setting state
    const [trades, setTrades ] = useState(tradeData); // this is the line with the issue
    console.log("trades in App is set to " + JSON.stringify(trades)); // `trades` is empty [] but `tradeData` has data, being evaluated later perhaps?
    const initialFormState = { id: '' , name: '', tradename: '' }; // not using all fields in form fyi
    const [ currentTrade, setCurrentTrade ] = useState(initialFormState)
    const [ editing, setEditing ] = useState(false)

    // CRUD operations
    const addTrade = trade => {
        trade.id = trades.length + 1
        setTrades([ ...trades, trade ])
    }

    const deleteTrade = id => {
        setEditing(false)

        setTrades(trades.filter(trade => trade.id !== id))
    }

    const updateTrade = (id, updatedTrade) => {
        setEditing(false)

        setTrades(trades.map(trade => (trade.id === id ? updatedTrade : trade)))
    }

    const editRow = trade => {
        setEditing(true)

        setCurrentTrade({ id: trade.id, name: trade.name, tradename: trade.tradename })
    }

    return (
        <div className="container">
            <h1>Trading App CRUD-style</h1>
            <div className="flex-row">
                <div className="flex-large">
                    {editing ? (
                        <Fragment>
                            <h2>Edit trade</h2>
                            <EditTradeForm
                                editing={editing}
                                setEditing={setEditing}
                                currentTrade={currentTrade}
                                updateTrade={updateTrade}
                            />
                        </Fragment>
                    ) : (
                        <Fragment>
                            <h2>Add trade</h2>
                            <AddTradeForm addTrade={addTrade} />
                        </Fragment>
                    )}
                </div>
                <div className="flex-large">
                    <h2>View trades</h2>
                    <TradeTable trades={trades} editRow={editRow} deleteTrade={deleteTrade} />
                </div>
            </div>
        </div>
    )
}

export default App

Hooks.js

import React, { useState, useEffect } from "react";
import axios from 'axios'

const useFetch = (httpurl, options) => {
const [mydata, setData] = useState([]);

  useEffect(() => {
    async function fetchData() {
       const response = await axios({
              method: 'POST',
              url : httpurl,
              headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
              },
              data: options
      });
      setData(response.data);  // what comes back is set to a JS variable called 'data'
    }
    fetchData();
  }, [httpurl]);

   return mydata;
};

export { useFetch };

tables/TradeTable.js

import React from 'react'

// this is where the App will process `trades` and process/render to view in a table, then take a CRUD action etc.

const TradeTable = props => (
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Tradename</th>
        <th>Trade Actions</th>
      </tr>
    </thead>
    <tbody>
      {props.trades.length > 0 ? (
        props.trades.map(trade => (
          <tr key={trade.id}>
            <td>{trade.name}</td>
            <td>{trade.tradename}</td>
            <td>
              <button
                onClick={() => {
                  props.editRow(trade)
                }}
                className="button muted-button"
              >
                Edit
              </button>
              <button
                onClick={() => props.deleteTrade(trade.id)}
                className="button muted-button"
              >
                Delete
              </button>
            </td>
          </tr>
        ))
      ) : (
        <tr>
          <td colSpan={3}>No trades</td>
        </tr>
      )}

So - how to ensure that trades[] is populated from the data coming from the axios call and represents the new State ? In particular, the 'View trade' bit further down App.js

much appreciated in advance for any guidance!

1
Can you please add the code for how you are passing the tradeData to TradeTable? as in the code you have shared there isn't any mention of how you are using TradeTable.Adarsh
have added further section in App.js - check out the <h2>View trades</h2> section.Paul O'Mahony

1 Answers

2
votes

From useState documentation:

The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded.

During initial render of App, tradeData is an empty array as fetch is not yet completed. tradeData is used only as initialState argument to useState.

Subsequently, when fetch is completed and tradeData holds the fetched data, this updated value of tradeData is not copied into trades state variable.

You can use an effect to copy the loaded tradeData to trades:

useEffect(() => {
    setTrades(tradeData)
}, [tradeData]);