Search code examples
javascriptreactjsecmascript-6async-awaitsetstate

Async/Await is not working as expected : ReactJS+ Async Await


I am having problem in executing API calls using aysnc and await. I am using a data grid which has data , column and set of dropdown options for each column to implement column filtering(data and the dropdown options I am fetching from server).

The column config array is dependent on these options where each column is tagged with options corresponding to it. I have extracted this column filter as a separate component where I pass this options .

Fetch API calls to this server work in such a way that every time I query I get an ID, and then this ID is being passed to the next function to get the actual data.

So I query the table data first, then get the dropdown values and then set the column objects so that table gets rendered properly.

But the problem here is , with the way I have written the code it should work correcly. But when I just load the page it gives me error inside getColumnFilterValues saying "cannot read param of undefined" . With async/await the dropdown values should be available prior to setting the column data. But in my case it throws the above error.

Can someone tell what is going wrong here?


import * as React from "react";
const FETCH_ID_URL = "/fetch/id";
const FETCH_DATA_URL = "/fetch/data";


export default class Test extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [], // hold table data
      columns: [], // hold column config
      dropDownValues: [], // hold column filter dropdown values
    };
  }

  async componentDidMount() {
    await this.fetchTableData(); // First fetching the table data
    await this.fetchDropDownValues(); // fetching the dropdown values for each column  
    this.setColumnData();  // then setting column object which is dependent on dropdown values
  }

  fetchDropDownValues = async () => {
    await this.fetchID(FETCH_ID_URL, payload, "dropDownValues");
  };

  fetchTableData = async () => {
    await this.fetchID(FETCH_ID_URL ,payload, "data");
  };

  fetchID = async (url, body, stateObject) => {
    try {
          const config = {
                          method: 'POST',
                          body: JSON.stringify(data)
                         }

         let response = await  fetch( url: FETCH_ID_URL, config);
         setTimeout(() => this.fetchData(response, stateObject), 2000); // Waiting for the ID to receive and then call fetchData
    } catch (e) {
      console.log(e);
    }
  };

  fetchData = async(obj: any, stateObject: string) => {
    try {
          const config = {
                          method: 'POST',
                          body: JSON.stringify(obj.id)
                         }
         let response = await  fetch( url: FETCH_DATA_URL, config);
         if (stateObject === "dropDownValues") {
           this.setState({ [stateObject]: response.dropdownData});
         } 
         else 
         {
            this.setState({[stateObject]: response.tableData});
         }
    } catch (e) {
      console.log(e);
    }
  };

  getValuesFromKey = (param: string) => {
    let data: any = this.state.dropDownValues[param]; //Throwing error here , giving cant read param of undefined
    let result = data.map((value: any) => {
      let keys = Object.keys(value);
      return {
        field: keys[0],
        checked: false,
      };
    });
    return result;
  };


  setColumnData = () => {
     let columns = [
      {
        Header: () => (
          <div>
              <Child
                name="firstName"
                options={this.getValuesFromKey("firstName")}
              />
            <span>First Name</span>
          </div>
        ),
        accessor: "firstName"
      },
      {
        Header: () => (
          <div>
              <Child
                name="status"
                options={this.getValuesFromKey("status")}
              />
            <span>Status</span>
          </div>
        ),
        accessor: "status",
      }
    ];
    this.setState({ columns });
  };

  render() {
    let { data, columns } = this.state;
    return (
       <ReactTable
          data={data}
          columns={columns}
        />
    );
  }
}



Solution

  • The problem is in this line:

    setTimeout(() => this.fetchData(response, stateObject), 2000); // Waiting for the ID to receive and then call fetchData
    

    this.setColumnData(); should be called after the setTimeout above has finished executing. In order to do so, you need to wrap the setTimeout in a Promise:

    return new Promise(resolve => {
      setTimeout(async () => {
        await this.fetchData(response, stateObject);
        resolve();
      }, 2000);
    });